blob: c5790ec45f74242349a8a3cca1f336e9d78cca88 [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'
21_DAYS_TO_BE_FAILING_TOO_LONG = 60
22_TEST_PASS_STATUS_INDEX = 6
23_MAIL_RESULTS_FROM = 'chromeos-test-health@google.com'
24_MAIL_RESULTS_TO = 'chromeos-lab-infrastructure@google.com'
25
26
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070027def load_storage():
28 """
29 Loads the storage object from disk.
30
31 This object keeps track of which tests we have already sent mail about so
32 we only send emails when the status of a test changes.
33
34 @return the storage object.
35
36 """
37 return shelve.open(_STORAGE_FILE)
38
39
40def save_storage(storage):
41 """
42 Saves the storage object to disk.
43
44 @param storage: The storage object to save to disk.
45
46 """
47 storage.close()
48
49
Keyar Hood005539d2013-06-24 14:27:57 -070050def is_valid_test_name(name):
51 """
52 Returns if a test name is valid or not.
53
54 There is a bunch of entries in the tko_test table that are not actually
55 test names. They are there as a side effect of how Autotest uses this
56 table.
57
58 Two examples of bad tests names are as follows:
59 link-release/R29-4228.0.0/faft_ec/firmware_ECPowerG3_SERVER_JOB
60 try_new_image-chormeos1-rack2-host2
61
62 @param name: The candidate test names to check.
63 @return True if name is a valid test name and false otherwise.
64
65 """
66 return not '/' in name and not name.startswith('try_new_image')
67
68
Keyar Hoodf9a36512013-06-13 18:39:56 -070069def get_last_pass_times():
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070070 """
71 Get all the tests that have passed and the time they last passed.
72
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070073 @return the dict of test_name:last_finish_time pairs for tests that have
74 passed.
75
76 """
Keyar Hoodf9a36512013-06-13 18:39:56 -070077 results = tko_models.Test.objects.values('test').filter(
78 status=_TEST_PASS_STATUS_INDEX).annotate(
79 last_pass=django_models.Max('started_time'))
Keyar Hood005539d2013-06-24 14:27:57 -070080 results_dict = {result['test']: result['last_pass']
81 for result in results}
82 valid_test_names = filter(is_valid_test_name, results_dict)
83 # The shelve module does not accept Unicode objects as keys but does
84 # accept utf-8 strings.
85 return {name.encode('utf8'): results_dict[name]
86 for name in valid_test_names}
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070087
88
Keyar Hoodf9a36512013-06-13 18:39:56 -070089def get_all_test_names():
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070090 """
91 Get all the test names from the database.
92
Keyar Hood1a3c8dd2013-05-29 17:41:50 -070093 @return a list of all the test names.
94
95 """
Keyar Hood005539d2013-06-24 14:27:57 -070096 results = tko_models.Test.objects.values('test').distinct()
97 test_names = [test['test'] for test in results]
98 valid_test_names = filter(is_valid_test_name, test_names)
99 return [test.encode('utf8') for test in valid_test_names]
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700100
101
Keyar Hoodf9a36512013-06-13 18:39:56 -0700102def get_tests_to_analyze():
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700103 """
104 Get all the tests as well as the last time they have passed.
105
106 The minimum datetime is given as last pass time for tests that have never
107 passed.
108
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700109 @return the dict of test_name:last_finish_time pairs.
110
111 """
Keyar Hoodf9a36512013-06-13 18:39:56 -0700112 last_passes = get_last_pass_times()
113 all_test_names = get_all_test_names()
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700114 failures_names = (set(all_test_names) - set(last_passes.keys()))
115 always_failed = {test: datetime.datetime.min for test in failures_names}
116 return dict(always_failed.items() + last_passes.items())
117
118
119def email_about_test_failure(tests, storage):
120 """
121 Send emails based on the last time tests has passed.
122
123 This involves updating the storage and sending an email if a test has
124 failed for a long time and we have not already sent an email about that
125 test.
126
127 @param tests: The test_name:time_of_last_pass pairs.
128 @param storage: The storage object.
129
130 """
131 failing_time_cutoff = datetime.timedelta(_DAYS_TO_BE_FAILING_TOO_LONG)
132 update_status = []
133
134 today = datetime.datetime.today()
135 for test, last_fail in tests.iteritems():
136 if today - last_fail >= failing_time_cutoff:
137 if test not in storage:
138 update_status.append(test)
139 storage[test] = today
140 else:
141 try:
142 del storage[test]
143 except KeyError:
144 pass
145
146 if update_status:
147 logging.info('Found %i new failing tests out %i, sending email.',
148 len(update_status),
149 len(tests))
150 mail.send(_MAIL_RESULTS_FROM,
151 [_MAIL_RESULTS_TO],
152 [],
153 'Long Failing Tests',
154 'The following tests have been failing for '
155 'at least %s days:\n\n' % (_DAYS_TO_BE_FAILING_TOO_LONG) +
156 '\n'.join(update_status))
157
158
159def main():
160 """
161 The script code.
162
163 Allows other python code to import and run this code. This will be more
164 important if a nice way to test this code can be determined.
165
166 """
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700167 storage = load_storage()
Keyar Hoodf9a36512013-06-13 18:39:56 -0700168 tests = get_tests_to_analyze()
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700169 email_about_test_failure(tests, storage)
170 save_storage(storage)
171
172 return 0
173
174
175if __name__ == '__main__':
176 sys.exit(main())