mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 1 | #!/usr/bin/python -u |
mbligh | c251454 | 2008-02-19 15:54:26 +0000 | [diff] [blame] | 2 | |
mbligh | b33e53e | 2008-06-17 19:41:26 +0000 | [diff] [blame] | 3 | import os, sys, optparse, fcntl, errno, traceback, socket |
mbligh | bb7b891 | 2006-10-08 03:59:02 +0000 | [diff] [blame] | 4 | |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 5 | import common |
jadmanski | db4f9b5 | 2008-12-03 22:52:53 +0000 | [diff] [blame] | 6 | from autotest_lib.client.common_lib import mail, pidfile |
| 7 | from autotest_lib.tko import db as tko_db, utils as tko_utils, status_lib, models |
mbligh | 9e93640 | 2009-05-13 20:42:17 +0000 | [diff] [blame] | 8 | from autotest_lib.client.common_lib import utils |
mbligh | 74fc046 | 2007-11-05 20:24:17 +0000 | [diff] [blame] | 9 | |
| 10 | |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 11 | def parse_args(): |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 12 | # build up our options parser and parse sys.argv |
| 13 | parser = optparse.OptionParser() |
| 14 | parser.add_option("-m", help="Send mail for FAILED tests", |
| 15 | dest="mailit", action="store_true") |
| 16 | parser.add_option("-r", help="Reparse the results of a job", |
| 17 | dest="reparse", action="store_true") |
| 18 | parser.add_option("-o", help="Parse a single results directory", |
| 19 | dest="singledir", action="store_true") |
| 20 | parser.add_option("-l", help=("Levels of subdirectories to include " |
| 21 | "in the job name"), |
| 22 | type="int", dest="level", default=1) |
| 23 | parser.add_option("-n", help="No blocking on an existing parse", |
| 24 | dest="noblock", action="store_true") |
| 25 | parser.add_option("-s", help="Database server hostname", |
| 26 | dest="db_host", action="store") |
| 27 | parser.add_option("-u", help="Database username", dest="db_user", |
| 28 | action="store") |
| 29 | parser.add_option("-p", help="Database password", dest="db_pass", |
| 30 | action="store") |
| 31 | parser.add_option("-d", help="Database name", dest="db_name", |
| 32 | action="store") |
mbligh | 9e93640 | 2009-05-13 20:42:17 +0000 | [diff] [blame] | 33 | parser.add_option("-P", help="Run site post-processing", |
| 34 | dest="site_do_post", action="store_true", default=False) |
jadmanski | d5ab8c5 | 2008-12-03 16:27:07 +0000 | [diff] [blame] | 35 | parser.add_option("--write-pidfile", |
| 36 | help="write pidfile (.parser_execute)", |
| 37 | dest="write_pidfile", action="store_true", |
| 38 | default=False) |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 39 | options, args = parser.parse_args() |
mbligh | 74fc046 | 2007-11-05 20:24:17 +0000 | [diff] [blame] | 40 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 41 | # we need a results directory |
| 42 | if len(args) == 0: |
| 43 | tko_utils.dprint("ERROR: at least one results directory must " |
| 44 | "be provided") |
| 45 | parser.print_help() |
| 46 | sys.exit(1) |
mbligh | 74fc046 | 2007-11-05 20:24:17 +0000 | [diff] [blame] | 47 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 48 | # pass the options back |
| 49 | return options, args |
mbligh | 74fc046 | 2007-11-05 20:24:17 +0000 | [diff] [blame] | 50 | |
| 51 | |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 52 | def format_failure_message(jobname, kernel, testname, status, reason): |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 53 | format_string = "%-12s %-20s %-12s %-10s %s" |
| 54 | return format_string % (jobname, kernel, testname, status, reason) |
mbligh | b85e6b0 | 2006-10-08 17:20:56 +0000 | [diff] [blame] | 55 | |
mbligh | bb7b891 | 2006-10-08 03:59:02 +0000 | [diff] [blame] | 56 | |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 57 | def mailfailure(jobname, job, message): |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 58 | message_lines = [""] |
| 59 | message_lines.append("The following tests FAILED for this job") |
| 60 | message_lines.append("http://%s/results/%s" % |
| 61 | (socket.gethostname(), jobname)) |
| 62 | message_lines.append("") |
| 63 | message_lines.append(format_failure_message("Job name", "Kernel", |
| 64 | "Test name", "FAIL/WARN", |
| 65 | "Failure reason")) |
| 66 | message_lines.append(format_failure_message("=" * 8, "=" * 6, "=" * 8, |
| 67 | "=" * 8, "=" * 14)) |
| 68 | message_header = "\n".join(message_lines) |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 69 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 70 | subject = "AUTOTEST: FAILED tests from job %s" % jobname |
| 71 | mail.send("", job.user, "", subject, message_header + message) |
mbligh | 006f230 | 2007-09-13 20:46:46 +0000 | [diff] [blame] | 72 | |
| 73 | |
jadmanski | 9b6babf | 2009-04-21 17:57:40 +0000 | [diff] [blame] | 74 | def find_old_tests(db, job_idx): |
| 75 | """ |
| 76 | Given a job index, return a list of all the test objects associated with |
| 77 | it in the database, including labels, but excluding other "complex" |
| 78 | data (attributes, iteration data, kernels). |
| 79 | """ |
| 80 | raw_tests = db.select("test_idx,subdir,test,started_time,finished_time", |
| 81 | "tests", {"job_idx": job_idx}) |
showard | 0f76934 | 2009-04-22 21:01:56 +0000 | [diff] [blame] | 82 | if not raw_tests: |
| 83 | return [] |
jadmanski | 9b6babf | 2009-04-21 17:57:40 +0000 | [diff] [blame] | 84 | |
| 85 | test_ids = ", ".join(str(raw_test[0]) for raw_test in raw_tests) |
| 86 | |
| 87 | labels = db.select("test_id, testlabel_id", "test_labels_tests", |
| 88 | "test_id in (%s)" % test_ids) |
| 89 | label_map = {} |
| 90 | for test_id, testlabel_id in labels: |
| 91 | label_map.setdefault(test_id, []).append(testlabel_id) |
| 92 | |
| 93 | tests = [] |
| 94 | for raw_test in raw_tests: |
| 95 | tests.append(models.test(raw_test[1], raw_test[2], None, None, None, |
| 96 | None, raw_test[3], raw_test[4], |
| 97 | [], {}, label_map.get(raw_test[0], []))) |
| 98 | return tests |
| 99 | |
| 100 | |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 101 | def parse_one(db, jobname, path, reparse, mail_on_failure): |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 102 | """ |
| 103 | Parse a single job. Optionally send email on failure. |
| 104 | """ |
| 105 | tko_utils.dprint("\nScanning %s (%s)" % (jobname, path)) |
jadmanski | 9b6babf | 2009-04-21 17:57:40 +0000 | [diff] [blame] | 106 | old_job_idx = db.find_job(jobname) |
showard | 181d289 | 2009-04-22 21:02:06 +0000 | [diff] [blame] | 107 | old_tests = [] |
jadmanski | 9b6babf | 2009-04-21 17:57:40 +0000 | [diff] [blame] | 108 | if reparse and old_job_idx: |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 109 | tko_utils.dprint("! Deleting old copy of job results to " |
| 110 | "reparse it") |
jadmanski | 9b6babf | 2009-04-21 17:57:40 +0000 | [diff] [blame] | 111 | old_tests = find_old_tests(db, old_job_idx) |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 112 | db.delete_job(jobname) |
| 113 | if db.find_job(jobname): |
| 114 | tko_utils.dprint("! Job is already parsed, done") |
| 115 | return |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 116 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 117 | # look up the status version |
jadmanski | db4f9b5 | 2008-12-03 22:52:53 +0000 | [diff] [blame] | 118 | job_keyval = models.job.read_keyval(path) |
| 119 | status_version = job_keyval.get("status_version", 0) |
jadmanski | 6e8bf75 | 2008-05-14 00:17:48 +0000 | [diff] [blame] | 120 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 121 | # parse out the job |
| 122 | parser = status_lib.parser(status_version) |
| 123 | job = parser.make_job(path) |
| 124 | status_log = os.path.join(path, "status.log") |
| 125 | if not os.path.exists(status_log): |
| 126 | status_log = os.path.join(path, "status") |
| 127 | if not os.path.exists(status_log): |
| 128 | tko_utils.dprint("! Unable to parse job, no status file") |
| 129 | return |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 130 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 131 | # parse the status logs |
| 132 | tko_utils.dprint("+ Parsing dir=%s, jobname=%s" % (path, jobname)) |
| 133 | status_lines = open(status_log).readlines() |
| 134 | parser.start(job) |
| 135 | tests = parser.end(status_lines) |
jadmanski | 9b6babf | 2009-04-21 17:57:40 +0000 | [diff] [blame] | 136 | |
| 137 | # parser.end can return the same object multiple times, so filter out dups |
| 138 | job.tests = [] |
| 139 | already_added = set() |
| 140 | for test in tests: |
| 141 | if test not in already_added: |
| 142 | already_added.add(test) |
| 143 | job.tests.append(test) |
| 144 | |
| 145 | # try and port labels over from the old tests, but if old tests stop |
| 146 | # matching up with new ones just give up |
| 147 | for test, old_test in zip(job.tests, old_tests): |
| 148 | tests_are_the_same = (test.testname == old_test.testname and |
| 149 | test.subdir == old_test.subdir and |
| 150 | test.started_time == old_test.started_time and |
| 151 | (test.finished_time == old_test.finished_time or |
| 152 | old_test.finished_time is None)) |
| 153 | if tests_are_the_same: |
| 154 | test.labels = old_test.labels |
| 155 | else: |
| 156 | tko_utils.dprint("! Reparse returned new tests, " |
| 157 | "dropping old test labels") |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 158 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 159 | # check for failures |
| 160 | message_lines = [""] |
| 161 | for test in job.tests: |
| 162 | if not test.subdir: |
| 163 | continue |
| 164 | tko_utils.dprint("* testname, status, reason: %s %s %s" |
| 165 | % (test.subdir, test.status, test.reason)) |
| 166 | if test.status in ("FAIL", "WARN"): |
| 167 | message_lines.append(format_failure_message( |
| 168 | jobname, test.kernel.base, test.subdir, |
| 169 | test.status, test.reason)) |
| 170 | message = "\n".join(message_lines) |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 171 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 172 | # send out a email report of failure |
| 173 | if len(message) > 2 and mail_on_failure: |
| 174 | tko_utils.dprint("Sending email report of failure on %s to %s" |
| 175 | % (jobname, job.user)) |
| 176 | mailfailure(jobname, job, message) |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 177 | |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 178 | # write the job into the database |
| 179 | db.insert_job(jobname, job) |
| 180 | db.commit() |
mbligh | 26b992b | 2008-02-19 15:46:21 +0000 | [diff] [blame] | 181 | |
| 182 | |
jadmanski | 8e9c257 | 2008-11-11 00:29:02 +0000 | [diff] [blame] | 183 | def _get_job_subdirs(path): |
| 184 | """ |
| 185 | Returns a list of job subdirectories at path. Returns None if the test |
| 186 | is itself a job directory. Does not recurse into the subdirs. |
| 187 | """ |
| 188 | # if there's a .machines file, use it to get the subdirs |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 189 | machine_list = os.path.join(path, ".machines") |
| 190 | if os.path.exists(machine_list): |
jadmanski | 42fbd07 | 2009-01-30 15:07:05 +0000 | [diff] [blame] | 191 | subdirs = set(line.strip() for line in file(machine_list)) |
| 192 | existing_subdirs = set(subdir for subdir in subdirs |
| 193 | if os.path.exists(os.path.join(path, subdir))) |
| 194 | if len(existing_subdirs) != 0: |
| 195 | return existing_subdirs |
jadmanski | 8e9c257 | 2008-11-11 00:29:02 +0000 | [diff] [blame] | 196 | |
| 197 | # if this dir contains ONLY subdirectories, return them |
| 198 | contents = set(os.listdir(path)) |
| 199 | contents.discard(".parse.lock") |
| 200 | subdirs = set(sub for sub in contents if |
| 201 | os.path.isdir(os.path.join(path, sub))) |
| 202 | if len(contents) == len(subdirs) != 0: |
| 203 | return subdirs |
| 204 | |
| 205 | # this is a job directory, or something else we don't understand |
| 206 | return None |
| 207 | |
| 208 | |
mbligh | a48eeb2 | 2009-03-11 16:44:43 +0000 | [diff] [blame] | 209 | def parse_leaf_path(db, path, level, reparse, mail_on_failure): |
| 210 | job_elements = path.split("/")[-level:] |
| 211 | jobname = "/".join(job_elements) |
| 212 | try: |
| 213 | db.run_with_retry(parse_one, db, jobname, path, reparse, |
| 214 | mail_on_failure) |
| 215 | except Exception: |
| 216 | traceback.print_exc() |
| 217 | |
| 218 | |
jadmanski | 8e9c257 | 2008-11-11 00:29:02 +0000 | [diff] [blame] | 219 | def parse_path(db, path, level, reparse, mail_on_failure): |
| 220 | job_subdirs = _get_job_subdirs(path) |
| 221 | if job_subdirs is not None: |
mbligh | a48eeb2 | 2009-03-11 16:44:43 +0000 | [diff] [blame] | 222 | # parse status.log in current directory, if it exists. multi-machine |
| 223 | # synchronous server side tests record output in this directory. without |
| 224 | # this check, we do not parse these results. |
| 225 | if os.path.exists(os.path.join(path, 'status.log')): |
| 226 | parse_leaf_path(db, path, level, reparse, mail_on_failure) |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 227 | # multi-machine job |
jadmanski | 8e9c257 | 2008-11-11 00:29:02 +0000 | [diff] [blame] | 228 | for subdir in job_subdirs: |
| 229 | jobpath = os.path.join(path, subdir) |
| 230 | parse_path(db, jobpath, level + 1, reparse, mail_on_failure) |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 231 | else: |
| 232 | # single machine job |
mbligh | a48eeb2 | 2009-03-11 16:44:43 +0000 | [diff] [blame] | 233 | parse_leaf_path(db, path, level, reparse, mail_on_failure) |
mbligh | bb7b891 | 2006-10-08 03:59:02 +0000 | [diff] [blame] | 234 | |
| 235 | |
mbligh | 9e93640 | 2009-05-13 20:42:17 +0000 | [diff] [blame] | 236 | def _site_post_parse_job_dummy(): |
| 237 | return {} |
| 238 | |
| 239 | |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 240 | def main(): |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 241 | options, args = parse_args() |
| 242 | results_dir = os.path.abspath(args[0]) |
| 243 | assert os.path.exists(results_dir) |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 244 | |
jadmanski | d5ab8c5 | 2008-12-03 16:27:07 +0000 | [diff] [blame] | 245 | pid_file_manager = pidfile.PidFileManager("parser", results_dir) |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 246 | |
jadmanski | d5ab8c5 | 2008-12-03 16:27:07 +0000 | [diff] [blame] | 247 | if options.write_pidfile: |
| 248 | pid_file_manager.open_file() |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 249 | |
mbligh | 9e93640 | 2009-05-13 20:42:17 +0000 | [diff] [blame] | 250 | site_post_parse_job = utils.import_site_function(__file__, |
| 251 | "autotest_lib.tko.site_parse", "site_post_parse_job", |
| 252 | _site_post_parse_job_dummy) |
| 253 | |
jadmanski | d5ab8c5 | 2008-12-03 16:27:07 +0000 | [diff] [blame] | 254 | try: |
| 255 | # build up the list of job dirs to parse |
| 256 | if options.singledir: |
| 257 | jobs_list = [results_dir] |
| 258 | else: |
| 259 | jobs_list = [os.path.join(results_dir, subdir) |
| 260 | for subdir in os.listdir(results_dir)] |
| 261 | |
| 262 | # build up the database |
| 263 | db = tko_db.db(autocommit=False, host=options.db_host, |
| 264 | user=options.db_user, password=options.db_pass, |
| 265 | database=options.db_name) |
| 266 | |
| 267 | # parse all the jobs |
| 268 | for path in jobs_list: |
| 269 | lockfile = open(os.path.join(path, ".parse.lock"), "w") |
| 270 | flags = fcntl.LOCK_EX |
| 271 | if options.noblock: |
mbligh | db18b0e | 2009-01-30 00:34:32 +0000 | [diff] [blame] | 272 | flags |= fcntl.LOCK_NB |
jadmanski | d5ab8c5 | 2008-12-03 16:27:07 +0000 | [diff] [blame] | 273 | try: |
| 274 | fcntl.flock(lockfile, flags) |
| 275 | except IOError, e: |
mbligh | db18b0e | 2009-01-30 00:34:32 +0000 | [diff] [blame] | 276 | # lock is not available and nonblock has been requested |
jadmanski | d5ab8c5 | 2008-12-03 16:27:07 +0000 | [diff] [blame] | 277 | if e.errno == errno.EWOULDBLOCK: |
| 278 | lockfile.close() |
| 279 | continue |
| 280 | else: |
| 281 | raise # something unexpected happened |
| 282 | try: |
| 283 | parse_path(db, path, options.level, options.reparse, |
| 284 | options.mailit) |
mbligh | 9e93640 | 2009-05-13 20:42:17 +0000 | [diff] [blame] | 285 | |
jadmanski | d5ab8c5 | 2008-12-03 16:27:07 +0000 | [diff] [blame] | 286 | finally: |
| 287 | fcntl.flock(lockfile, fcntl.LOCK_UN) |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 288 | lockfile.close() |
mbligh | e97e0e6 | 2009-05-21 01:41:58 +0000 | [diff] [blame^] | 289 | |
| 290 | if options.site_do_post is True: |
| 291 | site_post_parse_job(results_dir) |
| 292 | |
jadmanski | d5ab8c5 | 2008-12-03 16:27:07 +0000 | [diff] [blame] | 293 | except: |
| 294 | pid_file_manager.close_file(1) |
| 295 | raise |
| 296 | else: |
| 297 | pid_file_manager.close_file(0) |
mbligh | 71d340d | 2008-03-05 15:51:16 +0000 | [diff] [blame] | 298 | |
mbligh | 532cb27 | 2007-11-26 18:54:20 +0000 | [diff] [blame] | 299 | |
mbligh | 96cf051 | 2008-04-17 15:25:38 +0000 | [diff] [blame] | 300 | if __name__ == "__main__": |
jadmanski | 0afbb63 | 2008-06-06 21:10:57 +0000 | [diff] [blame] | 301 | main() |