blob: ef7971acaef1453206e73e9c53a366467b7a040f [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
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -07007from autotest_lib.tko import db as tko_db, utils as tko_utils
8from autotest_lib.tko import models, status_lib
9from autotest_lib.tko.perf_upload import perf_uploader
mbligh9e936402009-05-13 20:42:17 +000010from autotest_lib.client.common_lib import utils
mbligh74fc0462007-11-05 20:24:17 +000011
12
mbligh96cf0512008-04-17 15:25:38 +000013def parse_args():
jadmanski0afbb632008-06-06 21:10:57 +000014 # build up our options parser and parse sys.argv
15 parser = optparse.OptionParser()
16 parser.add_option("-m", help="Send mail for FAILED tests",
17 dest="mailit", action="store_true")
18 parser.add_option("-r", help="Reparse the results of a job",
19 dest="reparse", action="store_true")
20 parser.add_option("-o", help="Parse a single results directory",
21 dest="singledir", action="store_true")
22 parser.add_option("-l", help=("Levels of subdirectories to include "
23 "in the job name"),
24 type="int", dest="level", default=1)
25 parser.add_option("-n", help="No blocking on an existing parse",
26 dest="noblock", action="store_true")
27 parser.add_option("-s", help="Database server hostname",
28 dest="db_host", action="store")
29 parser.add_option("-u", help="Database username", dest="db_user",
30 action="store")
31 parser.add_option("-p", help="Database password", dest="db_pass",
32 action="store")
33 parser.add_option("-d", help="Database name", dest="db_name",
34 action="store")
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
mbligh96cf0512008-04-17 15:25:38 +000074def parse_one(db, jobname, path, reparse, mail_on_failure):
jadmanski0afbb632008-06-06 21:10:57 +000075 """
76 Parse a single job. Optionally send email on failure.
77 """
78 tko_utils.dprint("\nScanning %s (%s)" % (jobname, path))
jadmanski9b6babf2009-04-21 17:57:40 +000079 old_job_idx = db.find_job(jobname)
showard0fec8a02009-12-04 01:19:54 +000080 # old tests is a dict from tuple (test_name, subdir) to test_idx
81 old_tests = {}
82 if old_job_idx is not None:
83 if not reparse:
84 tko_utils.dprint("! Job is already parsed, done")
85 return
86
showardeab66ce2009-12-23 00:03:56 +000087 raw_old_tests = db.select("test_idx,subdir,test", "tko_tests",
showard0fec8a02009-12-04 01:19:54 +000088 {"job_idx": old_job_idx})
89 if raw_old_tests:
90 old_tests = dict(((test, subdir), test_idx)
91 for test_idx, subdir, test in raw_old_tests)
mbligh96cf0512008-04-17 15:25:38 +000092
jadmanski0afbb632008-06-06 21:10:57 +000093 # look up the status version
jadmanskidb4f9b52008-12-03 22:52:53 +000094 job_keyval = models.job.read_keyval(path)
95 status_version = job_keyval.get("status_version", 0)
jadmanski6e8bf752008-05-14 00:17:48 +000096
jadmanski0afbb632008-06-06 21:10:57 +000097 # parse out the job
98 parser = status_lib.parser(status_version)
99 job = parser.make_job(path)
100 status_log = os.path.join(path, "status.log")
101 if not os.path.exists(status_log):
102 status_log = os.path.join(path, "status")
103 if not os.path.exists(status_log):
104 tko_utils.dprint("! Unable to parse job, no status file")
105 return
mbligh96cf0512008-04-17 15:25:38 +0000106
jadmanski0afbb632008-06-06 21:10:57 +0000107 # parse the status logs
108 tko_utils.dprint("+ Parsing dir=%s, jobname=%s" % (path, jobname))
109 status_lines = open(status_log).readlines()
110 parser.start(job)
111 tests = parser.end(status_lines)
jadmanski9b6babf2009-04-21 17:57:40 +0000112
113 # parser.end can return the same object multiple times, so filter out dups
114 job.tests = []
115 already_added = set()
116 for test in tests:
117 if test not in already_added:
118 already_added.add(test)
119 job.tests.append(test)
120
showard0fec8a02009-12-04 01:19:54 +0000121 # try and port test_idx over from the old tests, but if old tests stop
jadmanski9b6babf2009-04-21 17:57:40 +0000122 # matching up with new ones just give up
showard0fec8a02009-12-04 01:19:54 +0000123 if reparse and old_job_idx is not None:
124 job.index = old_job_idx
125 for test in job.tests:
126 test_idx = old_tests.pop((test.testname, test.subdir), None)
127 if test_idx is not None:
128 test.test_idx = test_idx
129 else:
130 tko_utils.dprint("! Reparse returned new test "
131 "testname=%r subdir=%r" %
132 (test.testname, test.subdir))
133 for test_idx in old_tests.itervalues():
134 where = {'test_idx' : test_idx}
jamesrene660ed82010-08-05 19:57:46 +0000135 db.delete('tko_iteration_result', where)
Dennis Jeffrey368c54b2013-07-24 11:19:03 -0700136 db.delete('tko_iteration_perf_value', where)
jamesrene660ed82010-08-05 19:57:46 +0000137 db.delete('tko_iteration_attributes', where)
138 db.delete('tko_test_attributes', where)
139 db.delete('tko_test_labels_tests', {'test_id': test_idx})
140 db.delete('tko_tests', where)
mbligh96cf0512008-04-17 15:25:38 +0000141
jadmanski0afbb632008-06-06 21:10:57 +0000142 # check for failures
143 message_lines = [""]
144 for test in job.tests:
145 if not test.subdir:
146 continue
147 tko_utils.dprint("* testname, status, reason: %s %s %s"
148 % (test.subdir, test.status, test.reason))
149 if test.status in ("FAIL", "WARN"):
150 message_lines.append(format_failure_message(
151 jobname, test.kernel.base, test.subdir,
152 test.status, test.reason))
153 message = "\n".join(message_lines)
mbligh96cf0512008-04-17 15:25:38 +0000154
jadmanski0afbb632008-06-06 21:10:57 +0000155 # send out a email report of failure
156 if len(message) > 2 and mail_on_failure:
157 tko_utils.dprint("Sending email report of failure on %s to %s"
158 % (jobname, job.user))
159 mailfailure(jobname, job, message)
mbligh96cf0512008-04-17 15:25:38 +0000160
jadmanski0afbb632008-06-06 21:10:57 +0000161 # write the job into the database
162 db.insert_job(jobname, job)
jamesren7a522042010-06-10 22:53:55 +0000163
Dennis Jeffreyf9bef6c2013-08-05 11:01:27 -0700164 # Upload perf values to the perf dashboard, if applicable.
165 for test in job.tests:
166 perf_uploader.upload_test(job, test)
167
jamesren7a522042010-06-10 22:53:55 +0000168 # Serializing job into a binary file
169 try:
170 from autotest_lib.tko import tko_pb2
171 from autotest_lib.tko import job_serializer
172
173 serializer = job_serializer.JobSerializer()
jamesren4826cc42010-06-15 20:33:22 +0000174 binary_file_name = os.path.join(path, "job.serialize")
175 serializer.serialize_to_binary(job, jobname, binary_file_name)
176
177 if reparse:
178 site_export_file = "autotest_lib.tko.site_export"
179 site_export = utils.import_site_function(__file__,
180 site_export_file,
181 "site_export",
182 _site_export_dummy)
183 site_export(binary_file_name)
184
jamesren7a522042010-06-10 22:53:55 +0000185 except ImportError:
186 tko_utils.dprint("DEBUG: tko_pb2.py doesn't exist. Create by "
187 "compiling tko/tko.proto.")
188
jadmanski0afbb632008-06-06 21:10:57 +0000189 db.commit()
mbligh26b992b2008-02-19 15:46:21 +0000190
jamesren4826cc42010-06-15 20:33:22 +0000191def _site_export_dummy(binary_file_name):
192 pass
mbligh26b992b2008-02-19 15:46:21 +0000193
jadmanski8e9c2572008-11-11 00:29:02 +0000194def _get_job_subdirs(path):
195 """
196 Returns a list of job subdirectories at path. Returns None if the test
197 is itself a job directory. Does not recurse into the subdirs.
198 """
199 # if there's a .machines file, use it to get the subdirs
jadmanski0afbb632008-06-06 21:10:57 +0000200 machine_list = os.path.join(path, ".machines")
201 if os.path.exists(machine_list):
jadmanski42fbd072009-01-30 15:07:05 +0000202 subdirs = set(line.strip() for line in file(machine_list))
203 existing_subdirs = set(subdir for subdir in subdirs
204 if os.path.exists(os.path.join(path, subdir)))
205 if len(existing_subdirs) != 0:
206 return existing_subdirs
jadmanski8e9c2572008-11-11 00:29:02 +0000207
208 # if this dir contains ONLY subdirectories, return them
209 contents = set(os.listdir(path))
210 contents.discard(".parse.lock")
211 subdirs = set(sub for sub in contents if
212 os.path.isdir(os.path.join(path, sub)))
213 if len(contents) == len(subdirs) != 0:
214 return subdirs
215
216 # this is a job directory, or something else we don't understand
217 return None
218
219
mbligha48eeb22009-03-11 16:44:43 +0000220def parse_leaf_path(db, path, level, reparse, mail_on_failure):
221 job_elements = path.split("/")[-level:]
222 jobname = "/".join(job_elements)
223 try:
224 db.run_with_retry(parse_one, db, jobname, path, reparse,
225 mail_on_failure)
226 except Exception:
227 traceback.print_exc()
228
229
jadmanski8e9c2572008-11-11 00:29:02 +0000230def parse_path(db, path, level, reparse, mail_on_failure):
231 job_subdirs = _get_job_subdirs(path)
232 if job_subdirs is not None:
mbligha48eeb22009-03-11 16:44:43 +0000233 # parse status.log in current directory, if it exists. multi-machine
234 # synchronous server side tests record output in this directory. without
235 # this check, we do not parse these results.
236 if os.path.exists(os.path.join(path, 'status.log')):
237 parse_leaf_path(db, path, level, reparse, mail_on_failure)
jadmanski0afbb632008-06-06 21:10:57 +0000238 # multi-machine job
jadmanski8e9c2572008-11-11 00:29:02 +0000239 for subdir in job_subdirs:
240 jobpath = os.path.join(path, subdir)
241 parse_path(db, jobpath, level + 1, reparse, mail_on_failure)
jadmanski0afbb632008-06-06 21:10:57 +0000242 else:
243 # single machine job
mbligha48eeb22009-03-11 16:44:43 +0000244 parse_leaf_path(db, path, level, reparse, mail_on_failure)
mblighbb7b8912006-10-08 03:59:02 +0000245
246
mbligh96cf0512008-04-17 15:25:38 +0000247def main():
jadmanski0afbb632008-06-06 21:10:57 +0000248 options, args = parse_args()
249 results_dir = os.path.abspath(args[0])
250 assert os.path.exists(results_dir)
mbligh96cf0512008-04-17 15:25:38 +0000251
jadmanskid5ab8c52008-12-03 16:27:07 +0000252 pid_file_manager = pidfile.PidFileManager("parser", results_dir)
mbligh96cf0512008-04-17 15:25:38 +0000253
jadmanskid5ab8c52008-12-03 16:27:07 +0000254 if options.write_pidfile:
255 pid_file_manager.open_file()
mbligh96cf0512008-04-17 15:25:38 +0000256
jadmanskid5ab8c52008-12-03 16:27:07 +0000257 try:
258 # build up the list of job dirs to parse
259 if options.singledir:
260 jobs_list = [results_dir]
261 else:
262 jobs_list = [os.path.join(results_dir, subdir)
263 for subdir in os.listdir(results_dir)]
264
265 # build up the database
266 db = tko_db.db(autocommit=False, host=options.db_host,
267 user=options.db_user, password=options.db_pass,
268 database=options.db_name)
269
270 # parse all the jobs
271 for path in jobs_list:
272 lockfile = open(os.path.join(path, ".parse.lock"), "w")
273 flags = fcntl.LOCK_EX
274 if options.noblock:
mblighdb18b0e2009-01-30 00:34:32 +0000275 flags |= fcntl.LOCK_NB
jadmanskid5ab8c52008-12-03 16:27:07 +0000276 try:
277 fcntl.flock(lockfile, flags)
278 except IOError, e:
mblighdb18b0e2009-01-30 00:34:32 +0000279 # lock is not available and nonblock has been requested
jadmanskid5ab8c52008-12-03 16:27:07 +0000280 if e.errno == errno.EWOULDBLOCK:
281 lockfile.close()
282 continue
283 else:
284 raise # something unexpected happened
285 try:
286 parse_path(db, path, options.level, options.reparse,
287 options.mailit)
mbligh9e936402009-05-13 20:42:17 +0000288
jadmanskid5ab8c52008-12-03 16:27:07 +0000289 finally:
290 fcntl.flock(lockfile, fcntl.LOCK_UN)
jadmanski0afbb632008-06-06 21:10:57 +0000291 lockfile.close()
mblighe97e0e62009-05-21 01:41:58 +0000292
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()