blob: 6688e8f66f9fda4de7e931500e6a3c180555d09c [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
Keyar Hood4d5f9d12013-06-28 15:36:45 -07008import datetime, shelve, sys
Keyar Hood1a3c8dd2013-05-29 17:41:50 -07009
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
Keyar Hood3d807682013-07-01 15:13:05 -0700131def store_results(tests, storage):
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700132 """
Keyar Hood3d807682013-07-01 15:13:05 -0700133 Store information about tests that have been failing for a long time.
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700134
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700135
136 @param tests: The test_name:time_of_last_pass pairs.
137 @param storage: The storage object.
138
139 """
140 failing_time_cutoff = datetime.timedelta(_DAYS_TO_BE_FAILING_TOO_LONG)
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700141
142 today = datetime.datetime.today()
143 for test, last_fail in tests.iteritems():
144 if today - last_fail >= failing_time_cutoff:
145 if test not in storage:
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700146 storage[test] = today
147 else:
148 try:
149 del storage[test]
150 except KeyError:
151 pass
152
Keyar Hood3d807682013-07-01 15:13:05 -0700153
154def email_about_test_failure(storage):
155 """
156 Send an email about all the tests in the storage object if there are any.
157
158 @param storage: The storage object.
159
160 """
Keyar Hood4d5f9d12013-06-28 15:36:45 -0700161 if storage:
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700162 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) +
Keyar Hood4d5f9d12013-06-28 15:36:45 -0700168 '\n'.join(storage.keys()))
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700169
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 Hood3d807682013-07-01 15:13:05 -0700181 store_results(tests, storage)
182 email_about_test_failure(storage)
Keyar Hood1a3c8dd2013-05-29 17:41:50 -0700183 save_storage(storage)
184
185 return 0
186
187
188if __name__ == '__main__':
189 sys.exit(main())