4 Selects all rows and columns that satisfy the condition specified
5 and draws the matrix. There is a seperate SQL query made for every (x,y)
9 print "Content-type: text/html\n"
11 import sys
, os
, urllib
, cgi
, cgitb
, re
, datetime
, time
13 total_wall_time_start
= time
.time()
16 from autotest_lib
.tko
import display
, frontend
, db
, query_lib
17 from autotest_lib
.client
.common_lib
import kernel_versions
20 <form action="/tko/compose_query.cgi" method="get">
27 <a href="http://autotest.kernel.org/wiki/AutotestTKOCondition">Help</a>
32 <SELECT NAME="columns">
42 <input type="text" name="condition" size="30" value="%s">
43 <input type="hidden" name="title" value="%s">
45 <td align="center"><input type="submit" value="Submit">
50 <form action="/tko/save_query.cgi" method="get">
53 <td>Name your query: </td>
55 <input type="text" name="label" size="15" value="">
57 <td align="center"> <input type="submit" value="Save Query">
59 <td> <a href="/tko/query_history.cgi">View saved queries</a></td>
61 <input type="hidden" name="columns" value="%s">
62 <input type="hidden" name="rows" value="%s">
63 <input type="hidden" name="condition" value="%s">
72 'machine_group': 'hostname',
89 def parse_field(form
, form_field
, field_default
):
90 if not form_field
in form
:
92 field_input
= form
[form_field
].value
.lower()
93 if field_input
and field_input
in frontend
.test_view_field_dict
:
98 def parse_condition(form
, form_field
, field_default
):
99 if not form_field
in form
:
101 return form
[form_field
].value
104 form
= cgi
.FieldStorage()
106 title_field
= parse_condition(form
, 'title', '')
107 row
= parse_field(form
, 'rows', 'kernel')
108 column
= parse_field(form
, 'columns', 'machine_group')
109 condition_field
= parse_condition(form
, 'condition', '')
111 if 'brief' in form
.keys() and form
['brief'].value
<> '0':
112 display
.set_brief_mode()
114 ## caller can specify rows and columns that shall be included into the report
115 ## regardless of whether actual test data is available yet
116 force_row_field
= parse_condition(form
,'force_row','')
117 force_column_field
= parse_condition(form
,'force_column','')
120 def split_forced_fields(force_field
):
122 return force_field
.split()
126 force_row
= split_forced_fields(force_row_field
)
127 force_column
= split_forced_fields(force_column_field
)
133 def construct_link(x
, y
):
137 if condition_field
!= '':
138 condition_list
.append(condition_field
)
140 next_row
= next_field
[row
]
141 condition_list
.append("%s='%s'" % (row
, y
))
143 next_column
= next_field
[column
]
144 condition_list
.append("%s='%s'" % (column
, x
))
145 next_condition
= '&'.join(condition_list
)
146 link
= '/tko/compose_query.cgi?' + urllib
.urlencode({'columns': next_column
,
147 'rows': next_row
, 'condition': next_condition
,
148 'title': title_field
})
152 def construct_logs_link(x
, y
, job_tag
):
153 job_path
= frontend
.html_root
+ job_tag
+ '/'
155 if (row
== 'test' and
156 not y
.split('.')[0] in ('boot', 'build', 'install')):
158 if (column
== 'test' and
159 not x
.split('.')[0] in ('boot', 'build', 'install')):
161 return '/tko/retrieve_logs.cgi?' + urllib
.urlencode({'job' : job_path
,
165 def create_select_options(selected_val
):
167 for option
in sorted(frontend
.test_view_field_dict
.keys()):
168 if selected_val
== option
:
169 selected
= " SELECTED"
173 ret
+= '<OPTION VALUE="%s"%s>%s</OPTION>\n' % \
174 (option
, selected
, option
)
178 def map_kernel_base(kernel_name
):
179 ## insert <br> after each / in kernel name
180 ## but spare consequtive //
181 kernel_name
= kernel_name
.replace('/','/<br>')
182 kernel_name
= kernel_name
.replace('/<br>/<br>','//')
186 def header_tuneup(field_name
, header
):
187 ## header tune up depends on particular field name and may include:
188 ## - breaking header into several strings if it is long url
189 ## - creating date from datetime stamp
190 ## - possibly, expect more various refinements for different fields
191 if field_name
== 'kernel':
192 return map_kernel_base(header
)
197 # Kernel name mappings -- the kernels table 'printable' field is
198 # effectively a sortable identifier for the kernel It encodes the base
199 # release which is used for overall sorting, plus where patches are
200 # applied it adds an increasing pNNN patch combination identifier
201 # (actually the kernel_idx for the entry). This allows sorting
202 # as normal by the base kernel version and then sub-sorting by the
203 # "first time we saw" a patch combination which should keep them in
204 # approximatly date order. This patch identifier is not suitable
205 # for display, so we have to map it to a suitable html fragment for
206 # display. This contains the kernel base version plus the truncated
207 # names of all the patches,
210 # +add-new-string-functions-
211 # +x86-amd-thermal-interrupt
213 # This mapping is produced when the first mapping is request, with
214 # a single query over the patches table; the result is then cached.
216 # Note: that we only count a base version as patched if it contains
217 # patches which are not already "expressed" in the base version.
218 # This includes both -gitN and -mmN kernels.
219 map_kernel_map
= None
222 def map_kernel_init():
223 fields
= ['base', 'k.kernel_idx', 'name', 'url']
225 for (base
, idx
, name
, url
) in db_obj
.select(','.join(fields
),
226 'tko_kernels k, tko_patches p', 'k.kernel_idx=p.kernel_idx'):
227 match
= re
.match(r
'.*(-mm[0-9]+|-git[0-9]+)\.(bz2|gz)$', url
)
231 key
= base
+ ' p%d' % (idx
)
232 if not map.has_key(key
):
233 map[key
] = map_kernel_base(base
) + ' p%d' % (idx
)
234 map[key
] += ('<br>+<span title="' + name
+ '">' +
235 name
[0:25] + '</span>')
240 def map_kernel(name
):
241 global map_kernel_map
242 if map_kernel_map
is None:
243 map_kernel_map
= map_kernel_init()
245 if map_kernel_map
.has_key(name
):
246 return map_kernel_map
[name
]
248 return map_kernel_base(name
.split(' ')[0])
259 if condition_field
.strip() != '':
261 where
= query_lib
.parse_scrub_and_gen_condition(
262 condition_field
, frontend
.test_view_field_dict
)
263 print "<!-- where clause: %s -->" % (where
,)
265 msg
= "Unspecified error when parsing condition"
266 return [[display
.box(msg
)]]
268 wall_time_start
= time
.time()
270 ## Unfortunately, we can not request reasons of failure always
271 ## because it may result in an inflated size of data transfer
272 ## (at the moment we fetch 500 bytes of reason descriptions into
274 ## If 'status' in [row,column] then either width or height
275 ## of the table <=7, hence table is not really 2D, and
276 ## query_reason is relatively save.
277 ## At the same time view when either rows or columns grouped
278 ## by status is when users need reasons of failures the most.
280 ## TO DO: implement [Show/Hide reasons] button or link in
281 ## all views and make thorough performance testing
282 test_data
= frontend
.get_matrix_data(db_obj
, column
, row
, where
,
283 query_reasons
= ('status' in [row
,column
])
286 sql_wall_time
= time
.time() - wall_time_start
288 except db
.MySQLTooManyRows
, error
:
289 return [[display
.box(str(error
))]]
291 for f_row
in force_row
:
292 if not f_row
in test_data
.y_values
:
293 test_data
.y_values
.append(f_row
)
294 for f_column
in force_column
:
295 if not f_column
in test_data
.x_values
:
296 test_data
.x_values
.append(f_column
)
298 if not test_data
.y_values
:
299 msg
= "There are no results for this query (yet?)."
300 return [[display
.box(msg
)]]
302 dict_url
= {'columns': row
,
303 'rows': column
, 'condition': condition_field
,
304 'title': title_field
}
305 link
= '/tko/compose_query.cgi?' + urllib
.urlencode(dict_url
)
306 header_row
= [display
.box("<center>(Flip Axis)</center>", link
=link
)]
308 for x
in test_data
.x_values
:
310 if field_map
.has_key(column
):
311 dx
= field_map
[column
](x
)
312 x_header
= header_tuneup(column
, dx
)
313 link
= construct_link(x
, None)
314 header_row
.append(display
.box(x_header
,header
=True,link
=link
))
316 matrix
= [header_row
]
317 # For each row, we are looping horizontally over the columns.
318 for y
in test_data
.y_values
:
320 if field_map
.has_key(row
):
321 dy
= field_map
[row
](y
)
322 y_header
= header_tuneup(row
, dy
)
323 link
= construct_link(None, y
)
324 cur_row
= [display
.box(y_header
, header
=True, link
=link
)]
325 for x
in test_data
.x_values
:
326 ## next 2 lines: temporary, until non timestamped
327 ## records are in the database
328 if x
==datetime
.datetime(1970,1,1): x
= None
329 if y
==datetime
.datetime(1970,1,1): y
= None
331 box_data
= test_data
.data
[x
][y
]
333 cur_row
.append(display
.box(None, None,
334 row_label
=y
, column_label
=x
))
336 job_tag
= test_data
.data
[x
][y
].job_tag
338 link
= construct_logs_link(x
, y
, job_tag
)
340 link
= construct_link(x
, y
)
342 apnd
= display
.status_precounted_box(db_obj
, box_data
,
345 matrix
.append(cur_row
)
350 if display
.is_brief_mode():
351 ## create main grid table only as provided by gen_matrix()
352 display
.print_table(gen_matrix())
354 # create the actual page
355 print '<html><head><title>'
356 print 'Filtered Autotest Results'
357 print '</title></head><body>'
358 display
.print_main_header()
359 print html_header
% (create_select_options(column
),
360 create_select_options(row
),
361 condition_field
, title_field
,
363 column
,row
,condition_field
)
365 print '<h1> %s </h1>' % (title_field
)
366 print display
.color_keys_row()
367 display
.print_table(gen_matrix())
368 print display
.color_keys_row()
369 total_wall_time
= time
.time() - total_wall_time_start
371 perf_info
= '<p style="font-size:x-small;">'
372 perf_info
+= 'sql access wall time = %s secs,' % sql_wall_time
373 perf_info
+= 'total wall time = %s secs</p>' % total_wall_time
375 print '</body></html>'