Just like in the results interface, provide a way for users
to monitor certain conditions of their own tests by providing
a flexible test filter, that will locate the tests that attend
to a particular condition and calculate rates based on it, set
in the file global_config.ini.
Example: Let's suppose that we want to monitor tests that
failed and that do not have the test attribute 'bz', that is,
do not have a test attribute bz with a bugzilla number associated,
one could simply set on global_config.ini, on the session
SCHEDULER:
email_test_db_filter: 'status <> 'GOOD' AND `test_attribute_bz`.value IS NULL'
And this would give the list of tests that attend to the
above condition, a test count and a percentage of the tests
that attend that condition considering the total amount of
tests executed. The global_config.ini key is properly commented.
Also, introduce a new common_lib.utils function that generates
a pretty print representation of any matrix, that will
adjust the length of all columns automagically, making the
resulting e-mail report much prettier to see, even with very
large test names. Example:
header = ("Word1", "Word2", "Word3")
rows = [["Heeeeeeeeeeeeeey", "Hey", "Eh"],
["Orange", "Nonononono", "Pineapple"],
["Egg", "Spam", "Bacon"]]
result = matrix_to_string(rows, header)
Gives:
Word1 Word2 Word3
Heeeeeeeeeeeeeey Hey Eh
Orange Nonononono Pineapple
Egg Spam Bacon
Signed-off-by: Lucas Meneghel Rodrigues <[email protected]>
---
client/common_lib/utils.py | 38 +++++++++++++++
global_config.ini | 6 ++
scheduler/scheduler_models.py | 102 +++++++++++++++++++++++++++++++----------
3 files changed, 122 insertions(+), 24 deletions(-)
diff --git a/client/common_lib/utils.py b/client/common_lib/utils.py
index a117cec..827d2b2 100644
--- a/client/common_lib/utils.py
+++ b/client/common_lib/utils.py
@@ -202,6 +202,44 @@ def open_write_close(filename, data):
f.close()
+def print_matrix(matrix, header=None):
+ """
+ Return a pretty, aligned string representation of a nxm matrix.
+
+ This representation can be used to print any tabular data, such as
+ database results. It works by scanning the lengths of each element
+ in each column, and determining the format string dynamically.
+
+ @param matrix: Matrix representation (list with n rows of m elements).
+ @param header: Optional tuple with header elements to be displayed.
+ """
+ lengths = []
+ for row in matrix:
+ for column in row:
+ i = row.index(column)
+ cl = len(column)
+ try:
+ ml = lengths[i]
+ if cl > ml:
+ lengths[i] = cl
+ except IndexError:
+ lengths.append(cl)
+
+ lengths = tuple(lengths)
+ format_string = ""
+ for length in lengths:
+ format_string += "%-" + str(length) + "s "
+ format_string += "\n"
+
+ matrix_str = ""
+ if header:
+ matrix_str += format_string % header
+ for row in rows:
+ matrix_str += format_string % tuple(row)
+
+ return matrix_str
+
+
def read_keyval(path):
"""
Read a key-value pair format file into a dictionary, and return it.
diff --git a/global_config.ini b/global_config.ini
index 6ba8832..57f890f 100644
--- a/global_config.ini
+++ b/global_config.ini
@@ -64,6 +64,12 @@ enable_scheduler: True
notify_email:
notify_email_from:
notify_email_statuses: Completed,Failed,Aborted
+# Here, as on the web interface, you can set a filter string, and get
+# a table with jobs that match this filter, as well as the stats for
+# the tests that match the filter pattern on the status e-mail
+# Example:
+# email_test_db_filter: 'status <> 'GOOD' AND `test_attribute_bz`.value IS
NULL'
+email_test_db_filter:
max_processes_per_drone: 1000
max_jobs_started_per_cycle: 100
max_parse_processes: 5
diff --git a/scheduler/scheduler_models.py b/scheduler/scheduler_models.py
index 618add6..ade1d77 100644
--- a/scheduler/scheduler_models.py
+++ b/scheduler/scheduler_models.py
@@ -19,6 +19,7 @@ _drone_manager: reference to global DroneManager instance.
import datetime, itertools, logging, os, re, sys, time, weakref
from django.db import connection
from autotest_lib.client.common_lib import global_config, host_protections
+from autotest_lib.client.common_lib import global_config, utils
from autotest_lib.frontend.afe import models, model_attributes
from autotest_lib.database import database_connection
from autotest_lib.scheduler import drone_manager, email_manager
@@ -608,7 +609,7 @@ class HostQueueEntry(DBObject):
job_stats = Job(id=self.job.id).get_execution_details()
subject = ('Autotest | Job ID: %s "%s" | Status: %s ' %
- (self.job.id, self.job.name, status))
+ (self.job.id, self.job.name, status))
if hostname is not None:
subject += '| Hostname: %s ' % hostname
@@ -631,10 +632,20 @@ class HostQueueEntry(DBObject):
body += "User tests failed: %s\n" % job_stats['total_failed']
body += ("User tests success rate: %.2f %%\n" %
job_stats['success_rate'])
+
if job_stats['failed_rows']:
body += "Failures:\n"
body += job_stats['failed_rows']
+ if job_stats['filter']:
+ body += "Job Test Filter: %s" % job_stats['filter']
+ body += "User tests filtered: %s\n" % job_stats['total_filtered']
+ body += ("User tests filtered rate: %.2f %%\n" %
+ job_stats['filtered_rate'])
+ if job_stats['filtered_rows']:
+ body += "Filtered results:\n"
+ body += job_stats['filtered_rows']
+
return subject, body
@@ -865,6 +876,29 @@ class Job(DBObject):
@return: Dictionary with test execution details
"""
+ def _find_test_jobs(rows):
+ """
+ Here we are looking for tests such as SERVER_JOB and CLIENT_JOB.*
+ Those are autotest 'internal job' tests, so they should not be
+ counted when evaluating the test stats.
+
+ @param rows: List of rows (matrix) with database results.
+ """
+ job_test_pattern = re.compile('SERVER|CLIENT\\_JOB\.[\d]')
+ n_test_jobs = 0
+ for r in rows:
+ test_name = r[0]
+ if job_test_pattern.match(test_name):
+ n_test_jobs += 1
+
+ return n_test_jobs
+
+
+ filter = global_config.global_config.get_config_value('SCHEDULER',
+
'email_test_db_filter',
+ type=str,
+ default='')
+
stats = {}
rows = _db.execute("""
@@ -877,42 +911,62 @@ class Job(DBObject):
failed_rows = [r for r in rows if not 'GOOD' in r]
+ if filter:
+ filtered_rows = _db.execute("""
+ SELECT t.test, s.word, t.reason
+ FROM tko_tests AS t, tko_jobs AS j, tko_status AS s
+ WHERE t.job_idx = j.job_idx
+ AND s.status_idx = t.status
+ AND j.afe_job_id = %s
+ AND %s
+ """ % (self.id, filter))
+ else:
+ filtered_rows = []
- # Here we are looking for tests such as SERVER_JOB and CLIENT_JOB.*
- # Those are autotest 'internal job' tests, so they should not be
- # counted when evaluating the test stats
- job_test_pattern = re.compile('SERVER|CLIENT\\_JOB\.[\d]')
- n_test_jobs = 0
- for r in rows:
- test_name = r[0]
- if job_test_pattern.match(test_name):
- n_test_jobs += 1
-
- # Same for the failed jobs
- n_test_jobs_failed = 0
- for f in failed_rows:
- test_name_failed = f[0]
- if job_test_pattern.match(test_name_failed):
- n_test_jobs_failed += 1
+ n_test_jobs = _find_test_jobs(rows)
+ n_test_jobs_failed = _find_test_jobs(failed_rows)
+ if filter:
+ n_test_jobs_filtered = _find_test_jobs(filtered_rows)
+ else:
+ n_test_jobs_filtered = 0
total_executed = len(rows) - n_test_jobs
total_failed = len(failed_rows) - n_test_jobs_failed
+ if filter:
+ total_filtered = len(filtered_rows) - n_test_jobs_filtered
+ else:
+ total_filtered = 0
if total_executed > 0:
success_rate = 100 - ((total_failed / float(total_executed)) * 100)
+ if filter:
+ filtered_rate = (total_filtered / float(total_executed)) * 100
+ else:
+ filtered_rate = 0
else:
success_rate = 0
+ filtered_rate = 0
stats['total_executed'] = total_executed
stats['total_failed'] = total_failed
stats['total_passed'] = total_executed - total_failed
stats['success_rate'] = success_rate
+ stats['filter'] = filter
+ stats['total_filtered'] = total_filtered
+ stats['filtered_rate'] = filtered_rate
+
+ status_header = ("Test Name", "Status", "Reason")
+ if failed_rows:
+ stats['failed_rows'] = utils.matrix_to_string(failed_rows,
+ status_header)
+ else:
+ stats['failed_rows'] = ''
- failed_str = '%-30s %-10s %-40s\n' % ("Test Name", "Status", "Reason")
- for row in failed_rows:
- failed_str += '%-30s %-10s %-40s\n' % row
-
- stats['failed_rows'] = failed_str
+ if filtered_rows:
+ stats['filtered_rows'] = utils.matrix_to_string(filtered_rows,
+ status_header)
+ else:
+ stats['filtered_rows'] = ''
time_row = _db.execute("""
SELECT started_time, finished_time
@@ -925,8 +979,8 @@ class Job(DBObject):
delta = t_end - t_begin
minutes, seconds = divmod(delta.seconds, 60)
hours, minutes = divmod(minutes, 60)
- stats['execution_time'] = "%02d:%02d:%02d" % (
- hours, minutes, seconds)
+ stats['execution_time'] = ("%02d:%02d:%02d" %
+ (hours, minutes, seconds))
else:
stats['execution_time'] = '(none)'
--
1.7.2.3
_______________________________________________
Autotest mailing list
[email protected]
http://test.kernel.org/cgi-bin/mailman/listinfo/autotest