blob: e727014a10c0993212720146bf2c56f50051650a [file] [log] [blame]
Moises Osorio2dda22e2014-09-16 15:56:24 -07001import pickle, datetime, itertools, operator
showard35444862008-08-07 22:35:30 +00002from django.db import models as dbmodels
Jiaxi Luoef6f8a12014-06-19 15:49:41 -07003from autotest_lib.client.common_lib import priorities
showard35444862008-08-07 22:35:30 +00004from autotest_lib.frontend.afe import rpc_utils, model_logic
showard64a95952010-01-13 21:27:16 +00005from autotest_lib.frontend.afe import models as afe_models, readonly_connection
showard250d84d2010-01-12 21:59:48 +00006from autotest_lib.frontend.tko import models, tko_rpc_utils, graphing_utils
7from autotest_lib.frontend.tko import preconfigs
showard35444862008-08-07 22:35:30 +00008
9# table/spreadsheet view support
10
11def get_test_views(**filter_data):
12 return rpc_utils.prepare_for_serialization(
13 models.TestView.list_objects(filter_data))
14
15
16def get_num_test_views(**filter_data):
17 return models.TestView.query_count(filter_data)
18
19
showard8bfb5cb2009-10-07 20:49:15 +000020def get_group_counts(group_by, header_groups=None, fixed_headers=None,
showard8b0ea222009-12-23 19:23:03 +000021 extra_select_fields=None, **filter_data):
showard35444862008-08-07 22:35:30 +000022 """
23 Queries against TestView grouping by the specified fields and computings
24 counts for each group.
25 * group_by should be a list of field names.
26 * extra_select_fields can be used to specify additional fields to select
27 (usually for aggregate functions).
28 * header_groups can be used to get lists of unique combinations of group
29 fields. It should be a list of tuples of fields from group_by. It's
30 primarily for use by the spreadsheet view.
showardf2489522008-10-23 23:08:00 +000031 * fixed_headers can map header fields to lists of values. the header will
32 guaranteed to return exactly those value. this does not work together
33 with header_groups.
showard35444862008-08-07 22:35:30 +000034
35 Returns a dictionary with two keys:
36 * header_values contains a list of lists, one for each header group in
37 header_groups. Each list contains all the values for the corresponding
38 header group as tuples.
39 * groups contains a list of dicts, one for each row. Each dict contains
40 keys for each of the group_by fields, plus a 'group_count' key for the
41 total count in the group, plus keys for each of the extra_select_fields.
42 The keys for the extra_select_fields are determined by the "AS" alias of
43 the field.
44 """
showard8b0ea222009-12-23 19:23:03 +000045 query = models.TestView.objects.get_query_set_with_joins(filter_data)
showard8bfb5cb2009-10-07 20:49:15 +000046 # don't apply presentation yet, since we have extra selects to apply
47 query = models.TestView.query_objects(filter_data, initial_query=query,
48 apply_presentation=False)
showard7c199df2008-10-03 10:17:15 +000049 count_alias, count_sql = models.TestView.objects.get_count_sql(query)
showard8bfb5cb2009-10-07 20:49:15 +000050 query = query.extra(select={count_alias: count_sql})
51 if extra_select_fields:
52 query = query.extra(select=extra_select_fields)
showard8bfb5cb2009-10-07 20:49:15 +000053 query = models.TestView.apply_presentation(query, filter_data)
showard35444862008-08-07 22:35:30 +000054
showard8a6eb0c2008-10-01 11:38:59 +000055 group_processor = tko_rpc_utils.GroupDataProcessor(query, group_by,
showard8bfb5cb2009-10-07 20:49:15 +000056 header_groups or [],
57 fixed_headers or {})
showard8a6eb0c2008-10-01 11:38:59 +000058 group_processor.process_group_dicts()
59 return rpc_utils.prepare_for_serialization(group_processor.get_info_dict())
showard35444862008-08-07 22:35:30 +000060
61
62def get_num_groups(group_by, **filter_data):
63 """
64 Gets the count of unique groups with the given grouping fields.
65 """
showardd2b0c882009-10-19 18:34:11 +000066 query = models.TestView.objects.get_query_set_with_joins(filter_data)
67 query = models.TestView.query_objects(filter_data, initial_query=query)
showard35444862008-08-07 22:35:30 +000068 return models.TestView.objects.get_num_groups(query, group_by)
69
70
showard8c9b8392008-09-30 10:38:21 +000071def get_status_counts(group_by, header_groups=[], fixed_headers={},
showard8b0ea222009-12-23 19:23:03 +000072 **filter_data):
showard35444862008-08-07 22:35:30 +000073 """
74 Like get_group_counts, but also computes counts of passed, complete (and
75 valid), and incomplete tests, stored in keys "pass_count', 'complete_count',
76 and 'incomplete_count', respectively.
77 """
showard8c9b8392008-09-30 10:38:21 +000078 return get_group_counts(group_by, header_groups=header_groups,
79 fixed_headers=fixed_headers,
showard7c199df2008-10-03 10:17:15 +000080 extra_select_fields=tko_rpc_utils.STATUS_FIELDS,
81 **filter_data)
showard35444862008-08-07 22:35:30 +000082
83
showard8a6eb0c2008-10-01 11:38:59 +000084def get_latest_tests(group_by, header_groups=[], fixed_headers={},
showard8b0ea222009-12-23 19:23:03 +000085 extra_info=[], **filter_data):
showard8a6eb0c2008-10-01 11:38:59 +000086 """
87 Similar to get_status_counts, but return only the latest test result per
88 group. It still returns the same information (i.e. with pass count etc.)
showard79097322010-01-20 01:12:25 +000089 for compatibility. It includes an additional field "test_idx" with each
90 group.
showard77401f32009-05-26 19:34:05 +000091 @param extra_info a list containing the field names that should be returned
92 with each cell. The fields are returned in the extra_info
93 field of the return dictionary.
showard8a6eb0c2008-10-01 11:38:59 +000094 """
95 # find latest test per group
showard79097322010-01-20 01:12:25 +000096 initial_query = models.TestView.objects.get_query_set_with_joins(
97 filter_data)
98 query = models.TestView.query_objects(filter_data,
99 initial_query=initial_query,
showard8bfb5cb2009-10-07 20:49:15 +0000100 apply_presentation=False)
showard763fd242009-12-10 21:40:16 +0000101 query = query.exclude(status__in=tko_rpc_utils._INVALID_STATUSES)
showard8bfb5cb2009-10-07 20:49:15 +0000102 query = query.extra(
103 select={'latest_test_idx' : 'MAX(%s)' %
104 models.TestView.objects.get_key_on_this_table('test_idx')})
showard8bfb5cb2009-10-07 20:49:15 +0000105 query = models.TestView.apply_presentation(query, filter_data)
showard8a6eb0c2008-10-01 11:38:59 +0000106
107 group_processor = tko_rpc_utils.GroupDataProcessor(query, group_by,
108 header_groups,
showard8bfb5cb2009-10-07 20:49:15 +0000109 fixed_headers)
showard8a6eb0c2008-10-01 11:38:59 +0000110 group_processor.process_group_dicts()
111 info = group_processor.get_info_dict()
112
113 # fetch full info for these tests so we can access their statuses
114 all_test_ids = [group['latest_test_idx'] for group in info['groups']]
showard79097322010-01-20 01:12:25 +0000115 test_views = initial_query.in_bulk(all_test_ids)
showard8a6eb0c2008-10-01 11:38:59 +0000116
117 for group_dict in info['groups']:
118 test_idx = group_dict.pop('latest_test_idx')
119 group_dict['test_idx'] = test_idx
showard77401f32009-05-26 19:34:05 +0000120 test_view = test_views[test_idx]
121
122 tko_rpc_utils.add_status_counts(group_dict, test_view.status)
123 group_dict['extra_info'] = []
124 for field in extra_info:
125 group_dict['extra_info'].append(getattr(test_view, field))
126
127 return rpc_utils.prepare_for_serialization(info)
showard8a6eb0c2008-10-01 11:38:59 +0000128
129
showard35444862008-08-07 22:35:30 +0000130def get_job_ids(**filter_data):
131 """
132 Returns AFE job IDs for all tests matching the filters.
133 """
134 query = models.TestView.query_objects(filter_data)
135 job_ids = set()
136 for test_view in query.values('job_tag').distinct():
137 # extract job ID from tag
showardec281562009-02-07 02:10:27 +0000138 first_tag_component = test_view['job_tag'].split('-')[0]
139 try:
140 job_id = int(first_tag_component)
141 job_ids.add(job_id)
142 except ValueError:
143 # a nonstandard job tag, i.e. from contributed results
144 pass
showard35444862008-08-07 22:35:30 +0000145 return list(job_ids)
146
147
showarde732ee72008-09-23 19:15:43 +0000148# test detail view
149
jadmanski430dca92008-12-16 20:56:53 +0000150def _attributes_to_dict(attribute_list):
showardf8b19042009-05-12 17:22:49 +0000151 return dict((attribute.attribute, attribute.value)
152 for attribute in attribute_list)
jadmanski430dca92008-12-16 20:56:53 +0000153
154
155def _iteration_attributes_to_dict(attribute_list):
showardf8b19042009-05-12 17:22:49 +0000156 iter_keyfunc = operator.attrgetter('iteration')
jadmanski430dca92008-12-16 20:56:53 +0000157 attribute_list.sort(key=iter_keyfunc)
158 iterations = {}
159 for key, group in itertools.groupby(attribute_list, iter_keyfunc):
160 iterations[key] = _attributes_to_dict(group)
161 return iterations
162
163
showardf8b19042009-05-12 17:22:49 +0000164def _format_iteration_keyvals(test):
165 iteration_attr = _iteration_attributes_to_dict(test.iteration_attributes)
166 iteration_perf = _iteration_attributes_to_dict(test.iteration_results)
167
168 all_iterations = iteration_attr.keys() + iteration_perf.keys()
169 max_iterations = max(all_iterations + [0])
170
171 # merge the iterations into a single list of attr & perf dicts
172 return [{'attr': iteration_attr.get(index, {}),
173 'perf': iteration_perf.get(index, {})}
174 for index in xrange(1, max_iterations + 1)]
175
176
Eric Li6f27d4f2010-09-29 10:55:17 -0700177def _job_keyvals_to_dict(keyvals):
178 return dict((keyval.key, keyval.value) for keyval in keyvals)
179
180
showarde732ee72008-09-23 19:15:43 +0000181def get_detailed_test_views(**filter_data):
182 test_views = models.TestView.list_objects(filter_data)
Eric Li6f27d4f2010-09-29 10:55:17 -0700183
showardf8b19042009-05-12 17:22:49 +0000184 tests_by_id = models.Test.objects.in_bulk([test_view['test_idx']
185 for test_view in test_views])
186 tests = tests_by_id.values()
187 models.Test.objects.populate_relationships(tests, models.TestAttribute,
188 'attributes')
189 models.Test.objects.populate_relationships(tests, models.IterationAttribute,
190 'iteration_attributes')
191 models.Test.objects.populate_relationships(tests, models.IterationResult,
192 'iteration_results')
193 models.Test.objects.populate_relationships(tests, models.TestLabel,
194 'labels')
Eric Li6f27d4f2010-09-29 10:55:17 -0700195
196 jobs_by_id = models.Job.objects.in_bulk([test_view['job_idx']
197 for test_view in test_views])
198 jobs = jobs_by_id.values()
199 models.Job.objects.populate_relationships(jobs, models.JobKeyval,
200 'keyvals')
201
showarde732ee72008-09-23 19:15:43 +0000202 for test_view in test_views:
showardf8b19042009-05-12 17:22:49 +0000203 test = tests_by_id[test_view['test_idx']]
204 test_view['attributes'] = _attributes_to_dict(test.attributes)
205 test_view['iterations'] = _format_iteration_keyvals(test)
206 test_view['labels'] = [label.name for label in test.labels]
Eric Li6f27d4f2010-09-29 10:55:17 -0700207
208 job = jobs_by_id[test_view['job_idx']]
209 test_view['job_keyvals'] = _job_keyvals_to_dict(job.keyvals)
210
showarde732ee72008-09-23 19:15:43 +0000211 return rpc_utils.prepare_for_serialization(test_views)
212
Moises Osorio2dda22e2014-09-16 15:56:24 -0700213
214def get_tests_summary(job_names):
215 """
216 Gets the count summary of all passed and failed tests per suite.
217 @param job_names: Names of the suite jobs to get the summary from.
218 @returns: A summary of all the passed and failed tests per suite job.
219 """
220 # Take advantage of Django's literal escaping to prevent SQL injection
221 sql_list = ','.join(['%s'] * len(job_names))
222 query = ('''SELECT job_name, IF (status = 'GOOD', status, 'FAIL')
223 AS test_status, COUNT(*) num
224 FROM tko_test_view_2
225 WHERE job_name IN (%s)
226 AND test_name <> 'SERVER_JOB'
227 AND test_name NOT LIKE 'CLIENT_JOB%%%%'
228 AND status <> 'TEST_NA'
229 GROUP BY job_name, IF (status = 'GOOD', status, 'FAIL')'''
230 % sql_list)
231
232 cursor = readonly_connection.connection().cursor()
233 cursor.execute(query, job_names)
234 results = rpc_utils.fetchall_as_list_of_dicts(cursor)
235
236 summaries = {}
237 for result in results:
238 label = result['job_name']
239 status = 'passed' if result['test_status'] == 'GOOD' else 'failed'
240 summary = summaries.setdefault(label, {})
241 summary[status] = result['num']
242
243 return summaries
244
245
showard35444862008-08-07 22:35:30 +0000246# graphing view support
247
248def get_hosts_and_tests():
249 """\
250 Gets every host that has had a benchmark run on it. Additionally, also
251 gets a dictionary mapping the host names to the benchmarks.
252 """
253
254 host_info = {}
255 q = (dbmodels.Q(test_name__startswith='kernbench') |
256 dbmodels.Q(test_name__startswith='dbench') |
257 dbmodels.Q(test_name__startswith='tbench') |
258 dbmodels.Q(test_name__startswith='unixbench') |
259 dbmodels.Q(test_name__startswith='iozone'))
260 test_query = models.TestView.objects.filter(q).values(
261 'test_name', 'hostname', 'machine_idx').distinct()
262 for result_dict in test_query:
263 hostname = result_dict['hostname']
264 test = result_dict['test_name']
265 machine_idx = result_dict['machine_idx']
266 host_info.setdefault(hostname, {})
267 host_info[hostname].setdefault('tests', [])
268 host_info[hostname]['tests'].append(test)
269 host_info[hostname]['id'] = machine_idx
270 return rpc_utils.prepare_for_serialization(host_info)
271
272
showardfbdab0b2009-04-29 19:49:50 +0000273def create_metrics_plot(queries, plot, invert, drilldown_callback,
274 normalize=None):
275 return graphing_utils.create_metrics_plot(
276 queries, plot, invert, normalize, drilldown_callback=drilldown_callback)
showardce12f552008-09-19 00:48:59 +0000277
278
showardfbdab0b2009-04-29 19:49:50 +0000279def create_qual_histogram(query, filter_string, interval, drilldown_callback):
280 return graphing_utils.create_qual_histogram(
281 query, filter_string, interval, drilldown_callback=drilldown_callback)
showardce12f552008-09-19 00:48:59 +0000282
283
showarde5ae1652009-02-11 23:37:20 +0000284# TODO(showard) - this extremely generic RPC is used only by one place in the
285# client. We should come up with a more opaque RPC for that place to call and
286# get rid of this.
showardce12f552008-09-19 00:48:59 +0000287def execute_query_with_param(query, param):
showard56e93772008-10-06 10:06:22 +0000288 cursor = readonly_connection.connection().cursor()
showardce12f552008-09-19 00:48:59 +0000289 cursor.execute(query, param)
290 return cursor.fetchall()
291
292
showardce12f552008-09-19 00:48:59 +0000293def get_preconfig(name, type):
showarde5ae1652009-02-11 23:37:20 +0000294 return preconfigs.manager.get_preconfig(name, type)
showardce12f552008-09-19 00:48:59 +0000295
296
297def get_embedding_id(url_token, graph_type, params):
298 try:
299 model = models.EmbeddedGraphingQuery.objects.get(url_token=url_token)
300 except models.EmbeddedGraphingQuery.DoesNotExist:
301 params_str = pickle.dumps(params)
302 now = datetime.datetime.now()
303 model = models.EmbeddedGraphingQuery(url_token=url_token,
304 graph_type=graph_type,
305 params=params_str,
306 last_updated=now)
307 model.cached_png = graphing_utils.create_embedded_plot(model,
308 now.ctime())
309 model.save()
310
311 return model.id
312
313
314def get_embedded_query_url_token(id):
315 model = models.EmbeddedGraphingQuery.objects.get(id=id)
316 return model.url_token
317
318
showard35444862008-08-07 22:35:30 +0000319# test label management
320
321def add_test_label(name, description=None):
322 return models.TestLabel.add_object(name=name, description=description).id
323
324
325def modify_test_label(label_id, **data):
326 models.TestLabel.smart_get(label_id).update_object(data)
327
328
329def delete_test_label(label_id):
330 models.TestLabel.smart_get(label_id).delete()
331
332
333def get_test_labels(**filter_data):
334 return rpc_utils.prepare_for_serialization(
335 models.TestLabel.list_objects(filter_data))
336
337
338def get_test_labels_for_tests(**test_filter_data):
showard02813502008-08-20 20:52:56 +0000339 label_ids = models.TestView.objects.query_test_label_ids(test_filter_data)
340 labels = models.TestLabel.list_objects({'id__in' : label_ids})
showard35444862008-08-07 22:35:30 +0000341 return rpc_utils.prepare_for_serialization(labels)
342
343
344def test_label_add_tests(label_id, **test_filter_data):
showard02813502008-08-20 20:52:56 +0000345 test_ids = models.TestView.objects.query_test_ids(test_filter_data)
346 models.TestLabel.smart_get(label_id).tests.add(*test_ids)
showard35444862008-08-07 22:35:30 +0000347
348
349def test_label_remove_tests(label_id, **test_filter_data):
showard02813502008-08-20 20:52:56 +0000350 label = models.TestLabel.smart_get(label_id)
351
352 # only include tests that actually have this label
353 extra_where = test_filter_data.get('extra_where', '')
354 if extra_where:
355 extra_where = '(' + extra_where + ') AND '
showardeab66ce2009-12-23 00:03:56 +0000356 extra_where += 'tko_test_labels.id = %s' % label.id
showard02813502008-08-20 20:52:56 +0000357 test_filter_data['extra_where'] = extra_where
358 test_ids = models.TestView.objects.query_test_ids(test_filter_data)
359
360 label.tests.remove(*test_ids)
showard35444862008-08-07 22:35:30 +0000361
362
showardf8b19042009-05-12 17:22:49 +0000363# user-created test attributes
364
365def set_test_attribute(attribute, value, **test_filter_data):
366 """
367 * attribute - string name of attribute
368 * value - string, or None to delete an attribute
369 * test_filter_data - filter data to apply to TestView to choose tests to act
370 upon
371 """
372 assert test_filter_data # disallow accidental actions on all hosts
373 test_ids = models.TestView.objects.query_test_ids(test_filter_data)
374 tests = models.Test.objects.in_bulk(test_ids)
375
376 for test in tests.itervalues():
377 test.set_or_delete_attribute(attribute, value)
378
379
showard35444862008-08-07 22:35:30 +0000380# saved queries
381
382def get_saved_queries(**filter_data):
383 return rpc_utils.prepare_for_serialization(
384 models.SavedQuery.list_objects(filter_data))
385
386
387def add_saved_query(name, url_token):
388 name = name.strip()
showard64a95952010-01-13 21:27:16 +0000389 owner = afe_models.User.current_user().login
showard35444862008-08-07 22:35:30 +0000390 existing_list = list(models.SavedQuery.objects.filter(owner=owner,
391 name=name))
392 if existing_list:
393 query_object = existing_list[0]
394 query_object.url_token = url_token
395 query_object.save()
396 return query_object.id
397
398 return models.SavedQuery.add_object(owner=owner, name=name,
399 url_token=url_token).id
400
401
402def delete_saved_queries(id_list):
showard64a95952010-01-13 21:27:16 +0000403 user = afe_models.User.current_user().login
showard35444862008-08-07 22:35:30 +0000404 query = models.SavedQuery.objects.filter(id__in=id_list, owner=user)
405 if query.count() == 0:
406 raise model_logic.ValidationError('No such queries found for this user')
407 query.delete()
408
409
410# other
showardb7a52fd2009-04-27 20:10:56 +0000411def get_motd():
412 return rpc_utils.get_motd()
showard35444862008-08-07 22:35:30 +0000413
showard35444862008-08-07 22:35:30 +0000414
415def get_static_data():
416 result = {}
417 group_fields = []
418 for field in models.TestView.group_fields:
419 if field in models.TestView.extra_fields:
420 name = models.TestView.extra_fields[field]
421 else:
422 name = models.TestView.get_field_dict()[field].verbose_name
423 group_fields.append((name.capitalize(), field))
424 model_fields = [(field.verbose_name.capitalize(), field.column)
425 for field in models.TestView._meta.fields]
426 extra_fields = [(field_name.capitalize(), field_sql)
427 for field_sql, field_name
428 in models.TestView.extra_fields.iteritems()]
showardce12f552008-09-19 00:48:59 +0000429
430 benchmark_key = {
431 'kernbench' : 'elapsed',
432 'dbench' : 'throughput',
433 'tbench' : 'throughput',
434 'unixbench' : 'score',
435 'iozone' : '32768-4096-fwrite'
436 }
437
showardeab66ce2009-12-23 00:03:56 +0000438 tko_perf_view = [
showardce12f552008-09-19 00:48:59 +0000439 ['Test Index', 'test_idx'],
440 ['Job Index', 'job_idx'],
441 ['Test Name', 'test_name'],
442 ['Subdirectory', 'subdir'],
443 ['Kernel Index', 'kernel_idx'],
444 ['Status Index', 'status_idx'],
445 ['Reason', 'reason'],
446 ['Host Index', 'machine_idx'],
447 ['Test Started Time', 'test_started_time'],
448 ['Test Finished Time', 'test_finished_time'],
449 ['Job Tag', 'job_tag'],
450 ['Job Name', 'job_name'],
451 ['Owner', 'job_owner'],
452 ['Job Queued Time', 'job_queued_time'],
453 ['Job Started Time', 'job_started_time'],
454 ['Job Finished Time', 'job_finished_time'],
455 ['Hostname', 'hostname'],
456 ['Platform', 'platform'],
457 ['Machine Owner', 'machine_owner'],
458 ['Kernel Hash', 'kernel_hash'],
459 ['Kernel Base', 'kernel_base'],
460 ['Kernel', 'kernel'],
461 ['Status', 'status'],
462 ['Iteration Number', 'iteration'],
463 ['Performance Keyval (Key)', 'iteration_key'],
464 ['Performance Keyval (Value)', 'iteration_value'],
465 ]
showard35444862008-08-07 22:35:30 +0000466
Jiaxi Luoef6f8a12014-06-19 15:49:41 -0700467 result['priorities'] = priorities.Priority.choices()
showard35444862008-08-07 22:35:30 +0000468 result['group_fields'] = sorted(group_fields)
469 result['all_fields'] = sorted(model_fields + extra_fields)
470 result['test_labels'] = get_test_labels(sort_by=['name'])
showard250d84d2010-01-12 21:59:48 +0000471 result['current_user'] = rpc_utils.prepare_for_serialization(
showard64a95952010-01-13 21:27:16 +0000472 afe_models.User.current_user().get_object_dict())
showardce12f552008-09-19 00:48:59 +0000473 result['benchmark_key'] = benchmark_key
showardeab66ce2009-12-23 00:03:56 +0000474 result['tko_perf_view'] = tko_perf_view
475 result['tko_test_view'] = model_fields
showarde5ae1652009-02-11 23:37:20 +0000476 result['preconfigs'] = preconfigs.manager.all_preconfigs()
showardedd58972009-04-16 03:08:27 +0000477 result['motd'] = rpc_utils.get_motd()
showardce12f552008-09-19 00:48:59 +0000478
showard35444862008-08-07 22:35:30 +0000479 return result