blob: 64159d0eb4cdf4f7bc39ab4d6991cbdc7cde1174 [file] [log] [blame]
Keyar Hood1a3c8dd2013-05-29 17:41:50 -07001#!/usr/bin/python
2#
3# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7
8import datetime, logging, shelve, sys
9
10import common
Keyar Hoodf9a36512013-06-13 18:39:56 -070011from autotest_lib.client.common_lib import mail
12from autotest_lib.frontend import setup_django_readonly_environment
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070013
Keyar Hoodf9a36512013-06-13 18:39:56 -070014# Django and the models are only setup after
15# the setup_django_readonly_environment module is imported.
16from autotest_lib.frontend.tko import models as tko_models
17from django.db import models as django_models
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070018
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070019
20_STORAGE_FILE = 'failure_storage'
Keyar Hoode7b11842013-06-25 15:41:59 -070021# Mark a test as failing too long if it has not passed in this many days
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070022_DAYS_TO_BE_FAILING_TOO_LONG = 60
Keyar Hoode7b11842013-06-25 15:41:59 -070023# Ignore any tests that have not ran in this many days
24_DAYS_NOT_RUNNING_CUTOFF = 60
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070025_TEST_PASS_STATUS_INDEX = 6
26_MAIL_RESULTS_FROM = 'chromeos-test-health@google.com'
27_MAIL_RESULTS_TO = 'chromeos-lab-infrastructure@google.com'
28
29
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070030def load_storage():
31 """
32 Loads the storage object from disk.
33
34 This object keeps track of which tests we have already sent mail about so
35 we only send emails when the status of a test changes.
36
37 @return the storage object.
38
39 """
40 return shelve.open(_STORAGE_FILE)
41
42
43def save_storage(storage):
44 """
45 Saves the storage object to disk.
46
47 @param storage: The storage object to save to disk.
48
49 """
50 storage.close()
51
52
Keyar Hood005539d2013-06-24 14:27:57 -070053def is_valid_test_name(name):
54 """
55 Returns if a test name is valid or not.
56
57 There is a bunch of entries in the tko_test table that are not actually
58 test names. They are there as a side effect of how Autotest uses this
59 table.
60
61 Two examples of bad tests names are as follows:
62 link-release/R29-4228.0.0/faft_ec/firmware_ECPowerG3_SERVER_JOB
63 try_new_image-chormeos1-rack2-host2
64
65 @param name: The candidate test names to check.
66 @return True if name is a valid test name and false otherwise.
67
68 """
69 return not '/' in name and not name.startswith('try_new_image')
70
71
Keyar Hoodf9a36512013-06-13 18:39:56 -070072def get_last_pass_times():
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070073 """
74 Get all the tests that have passed and the time they last passed.
75
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070076 @return the dict of test_name:last_finish_time pairs for tests that have
77 passed.
78
79 """
Keyar Hoodf9a36512013-06-13 18:39:56 -070080 results = tko_models.Test.objects.values('test').filter(
81 status=_TEST_PASS_STATUS_INDEX).annotate(
82 last_pass=django_models.Max('started_time'))
Keyar Hood005539d2013-06-24 14:27:57 -070083 results_dict = {result['test']: result['last_pass']
84 for result in results}
85 valid_test_names = filter(is_valid_test_name, results_dict)
86 # The shelve module does not accept Unicode objects as keys but does
87 # accept utf-8 strings.
88 return {name.encode('utf8'): results_dict[name]
89 for name in valid_test_names}
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070090
91
Keyar Hoode7b11842013-06-25 15:41:59 -070092def get_recently_ran_test_names():
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070093 """
Keyar Hoode7b11842013-06-25 15:41:59 -070094 Get all the test names from the database that have been recently ran.
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070095
Keyar Hoode7b11842013-06-25 15:41:59 -070096 @return a set of the recently ran tests.
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070097
98 """
Keyar Hoode7b11842013-06-25 15:41:59 -070099 cutoff_delta = datetime.timedelta(_DAYS_NOT_RUNNING_CUTOFF)
100 cutoff_date = datetime.datetime.today() - cutoff_delta
101 results = tko_models.Test.objects.filter(
102 started_time__gte=cutoff_date).values('test').distinct()
Keyar Hood005539d2013-06-24 14:27:57 -0700103 test_names = [test['test'] for test in results]
104 valid_test_names = filter(is_valid_test_name, test_names)
Keyar Hoode7b11842013-06-25 15:41:59 -0700105 return {test.encode('utf8') for test in valid_test_names}
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700106
107
Keyar Hoodf9a36512013-06-13 18:39:56 -0700108def get_tests_to_analyze():
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700109 """
Keyar Hoode7b11842013-06-25 15:41:59 -0700110 Get all the recently ran tests as well as the last time they have passed.
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700111
112 The minimum datetime is given as last pass time for tests that have never
113 passed.
114
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700115 @return the dict of test_name:last_finish_time pairs.
116
117 """
Keyar Hoode7b11842013-06-25 15:41:59 -0700118 recent_test_names = get_recently_ran_test_names()
Keyar Hoodf9a36512013-06-13 18:39:56 -0700119 last_passes = get_last_pass_times()
Keyar Hoode7b11842013-06-25 15:41:59 -0700120
121 running_passes = {}
122 for test, pass_time in last_passes.items():
123 if test in recent_test_names:
124 running_passes[test] = pass_time
125
126 failures_names = recent_test_names.difference(running_passes)
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700127 always_failed = {test: datetime.datetime.min for test in failures_names}
Keyar Hoode7b11842013-06-25 15:41:59 -0700128 return dict(always_failed.items() + running_passes.items())
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700129
130
131def email_about_test_failure(tests, storage):
132 """
133 Send emails based on the last time tests has passed.
134
135 This involves updating the storage and sending an email if a test has
136 failed for a long time and we have not already sent an email about that
137 test.
138
139 @param tests: The test_name:time_of_last_pass pairs.
140 @param storage: The storage object.
141
142 """
143 failing_time_cutoff = datetime.timedelta(_DAYS_TO_BE_FAILING_TOO_LONG)
144 update_status = []
145
146 today = datetime.datetime.today()
147 for test, last_fail in tests.iteritems():
148 if today - last_fail >= failing_time_cutoff:
149 if test not in storage:
150 update_status.append(test)
151 storage[test] = today
152 else:
153 try:
154 del storage[test]
155 except KeyError:
156 pass
157
158 if update_status:
159 logging.info('Found %i new failing tests out %i, sending email.',
160 len(update_status),
161 len(tests))
162 mail.send(_MAIL_RESULTS_FROM,
163 [_MAIL_RESULTS_TO],
164 [],
165 'Long Failing Tests',
166 'The following tests have been failing for '
167 'at least %s days:\n\n' % (_DAYS_TO_BE_FAILING_TOO_LONG) +
168 '\n'.join(update_status))
169
170
171def main():
172 """
173 The script code.
174
175 Allows other python code to import and run this code. This will be more
176 important if a nice way to test this code can be determined.
177
178 """
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700179 storage = load_storage()
Keyar Hoodf9a36512013-06-13 18:39:56 -0700180 tests = get_tests_to_analyze()
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700181 email_about_test_failure(tests, storage)
182 save_storage(storage)
183
184 return 0
185
186
187if __name__ == '__main__':
188 sys.exit(main())