blob: 67f76a5bba7a419253da1424b89e1b9e380d1170 [file] [log] [blame]
mbligh96cf0512008-04-17 15:25:38 +00001#!/usr/bin/python -u
mblighc2514542008-02-19 15:54:26 +00002
mblighb33e53e2008-06-17 19:41:26 +00003import os, sys, optparse, fcntl, errno, traceback, socket
mblighbb7b8912006-10-08 03:59:02 +00004
mbligh96cf0512008-04-17 15:25:38 +00005import common
jadmanskidb4f9b52008-12-03 22:52:53 +00006from autotest_lib.client.common_lib import mail, pidfile
7from autotest_lib.tko import db as tko_db, utils as tko_utils, status_lib, models
mbligh9e936402009-05-13 20:42:17 +00008from autotest_lib.client.common_lib import utils
mbligh74fc0462007-11-05 20:24:17 +00009
10
mbligh96cf0512008-04-17 15:25:38 +000011def parse_args():
jadmanski0afbb632008-06-06 21:10:57 +000012 # 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")
mbligh9e936402009-05-13 20:42:17 +000033 parser.add_option("-P", help="Run site post-processing",
34 dest="site_do_post", action="store_true", default=False)
jadmanskid5ab8c52008-12-03 16:27:07 +000035 parser.add_option("--write-pidfile",
36 help="write pidfile (.parser_execute)",
37 dest="write_pidfile", action="store_true",
38 default=False)
jadmanski0afbb632008-06-06 21:10:57 +000039 options, args = parser.parse_args()
mbligh74fc0462007-11-05 20:24:17 +000040
jadmanski0afbb632008-06-06 21:10:57 +000041 # 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)
mbligh74fc0462007-11-05 20:24:17 +000047
jadmanski0afbb632008-06-06 21:10:57 +000048 # pass the options back
49 return options, args
mbligh74fc0462007-11-05 20:24:17 +000050
51
mbligh96cf0512008-04-17 15:25:38 +000052def format_failure_message(jobname, kernel, testname, status, reason):
jadmanski0afbb632008-06-06 21:10:57 +000053 format_string = "%-12s %-20s %-12s %-10s %s"
54 return format_string % (jobname, kernel, testname, status, reason)
mblighb85e6b02006-10-08 17:20:56 +000055
mblighbb7b8912006-10-08 03:59:02 +000056
mbligh96cf0512008-04-17 15:25:38 +000057def mailfailure(jobname, job, message):
jadmanski0afbb632008-06-06 21:10:57 +000058 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)
mbligh96cf0512008-04-17 15:25:38 +000069
jadmanski0afbb632008-06-06 21:10:57 +000070 subject = "AUTOTEST: FAILED tests from job %s" % jobname
71 mail.send("", job.user, "", subject, message_header + message)
mbligh006f2302007-09-13 20:46:46 +000072
73
jadmanski9b6babf2009-04-21 17:57:40 +000074def 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})
showard0f769342009-04-22 21:01:56 +000082 if not raw_tests:
83 return []
jadmanski9b6babf2009-04-21 17:57:40 +000084
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
mbligh96cf0512008-04-17 15:25:38 +0000101def parse_one(db, jobname, path, reparse, mail_on_failure):
jadmanski0afbb632008-06-06 21:10:57 +0000102 """
103 Parse a single job. Optionally send email on failure.
104 """
105 tko_utils.dprint("\nScanning %s (%s)" % (jobname, path))
jadmanski9b6babf2009-04-21 17:57:40 +0000106 old_job_idx = db.find_job(jobname)
showard181d2892009-04-22 21:02:06 +0000107 old_tests = []
jadmanski9b6babf2009-04-21 17:57:40 +0000108 if reparse and old_job_idx:
jadmanski0afbb632008-06-06 21:10:57 +0000109 tko_utils.dprint("! Deleting old copy of job results to "
110 "reparse it")
jadmanski9b6babf2009-04-21 17:57:40 +0000111 old_tests = find_old_tests(db, old_job_idx)
jadmanski0afbb632008-06-06 21:10:57 +0000112 db.delete_job(jobname)
113 if db.find_job(jobname):
114 tko_utils.dprint("! Job is already parsed, done")
115 return
mbligh96cf0512008-04-17 15:25:38 +0000116
jadmanski0afbb632008-06-06 21:10:57 +0000117 # look up the status version
jadmanskidb4f9b52008-12-03 22:52:53 +0000118 job_keyval = models.job.read_keyval(path)
119 status_version = job_keyval.get("status_version", 0)
jadmanski6e8bf752008-05-14 00:17:48 +0000120
jadmanski0afbb632008-06-06 21:10:57 +0000121 # 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
mbligh96cf0512008-04-17 15:25:38 +0000130
jadmanski0afbb632008-06-06 21:10:57 +0000131 # 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)
jadmanski9b6babf2009-04-21 17:57:40 +0000136
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")
mbligh96cf0512008-04-17 15:25:38 +0000158
jadmanski0afbb632008-06-06 21:10:57 +0000159 # 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)
mbligh96cf0512008-04-17 15:25:38 +0000171
jadmanski0afbb632008-06-06 21:10:57 +0000172 # 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)
mbligh96cf0512008-04-17 15:25:38 +0000177
jadmanski0afbb632008-06-06 21:10:57 +0000178 # write the job into the database
179 db.insert_job(jobname, job)
180 db.commit()
mbligh26b992b2008-02-19 15:46:21 +0000181
182
jadmanski8e9c2572008-11-11 00:29:02 +0000183def _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
jadmanski0afbb632008-06-06 21:10:57 +0000189 machine_list = os.path.join(path, ".machines")
190 if os.path.exists(machine_list):
jadmanski42fbd072009-01-30 15:07:05 +0000191 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
jadmanski8e9c2572008-11-11 00:29:02 +0000196
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
mbligha48eeb22009-03-11 16:44:43 +0000209def 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
jadmanski8e9c2572008-11-11 00:29:02 +0000219def parse_path(db, path, level, reparse, mail_on_failure):
220 job_subdirs = _get_job_subdirs(path)
221 if job_subdirs is not None:
mbligha48eeb22009-03-11 16:44:43 +0000222 # 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)
jadmanski0afbb632008-06-06 21:10:57 +0000227 # multi-machine job
jadmanski8e9c2572008-11-11 00:29:02 +0000228 for subdir in job_subdirs:
229 jobpath = os.path.join(path, subdir)
230 parse_path(db, jobpath, level + 1, reparse, mail_on_failure)
jadmanski0afbb632008-06-06 21:10:57 +0000231 else:
232 # single machine job
mbligha48eeb22009-03-11 16:44:43 +0000233 parse_leaf_path(db, path, level, reparse, mail_on_failure)
mblighbb7b8912006-10-08 03:59:02 +0000234
235
mbligh9e936402009-05-13 20:42:17 +0000236def _site_post_parse_job_dummy():
237 return {}
238
239
mbligh96cf0512008-04-17 15:25:38 +0000240def main():
jadmanski0afbb632008-06-06 21:10:57 +0000241 options, args = parse_args()
242 results_dir = os.path.abspath(args[0])
243 assert os.path.exists(results_dir)
mbligh96cf0512008-04-17 15:25:38 +0000244
jadmanskid5ab8c52008-12-03 16:27:07 +0000245 pid_file_manager = pidfile.PidFileManager("parser", results_dir)
mbligh96cf0512008-04-17 15:25:38 +0000246
jadmanskid5ab8c52008-12-03 16:27:07 +0000247 if options.write_pidfile:
248 pid_file_manager.open_file()
mbligh96cf0512008-04-17 15:25:38 +0000249
mbligh9e936402009-05-13 20:42:17 +0000250 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
jadmanskid5ab8c52008-12-03 16:27:07 +0000254 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:
mblighdb18b0e2009-01-30 00:34:32 +0000272 flags |= fcntl.LOCK_NB
jadmanskid5ab8c52008-12-03 16:27:07 +0000273 try:
274 fcntl.flock(lockfile, flags)
275 except IOError, e:
mblighdb18b0e2009-01-30 00:34:32 +0000276 # lock is not available and nonblock has been requested
jadmanskid5ab8c52008-12-03 16:27:07 +0000277 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)
mbligh9e936402009-05-13 20:42:17 +0000285
jadmanskid5ab8c52008-12-03 16:27:07 +0000286 finally:
287 fcntl.flock(lockfile, fcntl.LOCK_UN)
jadmanski0afbb632008-06-06 21:10:57 +0000288 lockfile.close()
mblighe97e0e62009-05-21 01:41:58 +0000289
290 if options.site_do_post is True:
291 site_post_parse_job(results_dir)
292
jadmanskid5ab8c52008-12-03 16:27:07 +0000293 except:
294 pid_file_manager.close_file(1)
295 raise
296 else:
297 pid_file_manager.close_file(0)
mbligh71d340d2008-03-05 15:51:16 +0000298
mbligh532cb272007-11-26 18:54:20 +0000299
mbligh96cf0512008-04-17 15:25:38 +0000300if __name__ == "__main__":
jadmanski0afbb632008-06-06 21:10:57 +0000301 main()