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

Reply via email to