blob: 5200f5248ca8e6b6a9a1af6538bbeaa8f2947668 [file] [log] [blame]
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Eric Lie7c4cab2011-01-05 14:39:19 -08002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
Mark Koudritsky1752f9f2012-05-16 17:12:14 -04005import logging, os, re, time
barfab@chromium.orgb6d29932012-04-11 09:46:43 +02006
Eric Lie7c4cab2011-01-05 14:39:19 -08007import common
Eric Lie7c4cab2011-01-05 14:39:19 -08008from autotest_lib.client.bin import utils
9from autotest_lib.client.common_lib import error
barfab@chromium.orgb6d29932012-04-11 09:46:43 +020010from constants import CLEANUP_LOGS_PAUSED_FILE
Eric Lie7c4cab2011-01-05 14:39:19 -080011
Mark Koudritsky1752f9f2012-05-16 17:12:14 -040012
13def extract_kernel_timestamp(msg):
14 """Extract a timestmap that appears in kernel log messages and looks
15 like this:
16 ... kernel: [78791.721832] ...
17
18 Returns:
19 The timestamp as float in seconds since last boot.
20 """
21
22 match = re.search(' \[\s*([0-9.]+)\] ', msg)
23 if match:
24 return float(match.group(1))
25 raise error.TestError('Could not extract timestamp from message: ' + msg)
26
27
Eric Lie7c4cab2011-01-05 14:39:19 -080028class LogReader(object):
29 """
30 A class to read system log files.
31 """
32
Eric Li4844a9a2011-04-03 14:42:49 -070033 def __init__(self, filename='/var/log/messages', include_rotated_logs=True):
Eric Lie7c4cab2011-01-05 14:39:19 -080034 self._start_line = 1
35 self._filename = filename
Eric Li4844a9a2011-04-03 14:42:49 -070036 self._include_rotated_logs = include_rotated_logs
Eric Lie7c4cab2011-01-05 14:39:19 -080037 if not os.path.exists(CLEANUP_LOGS_PAUSED_FILE):
38 raise error.TestError('LogReader created without ' +
39 CLEANUP_LOGS_PAUSED_FILE)
40
41
Eric Li4844a9a2011-04-03 14:42:49 -070042 def read_all_logs(self, start=0):
43 """Read all content from log files.
44
45 Generator function.
46 Return an iterator on the content of files.
47 """
48 log_files = []
49 line_number = 1
50 if self._include_rotated_logs:
51 log_files.extend(utils.system_output(
52 'ls -tr1 %s.*' % self._filename,
53 ignore_status=True).splitlines())
54 log_files.append(self._filename)
Eric Li4844a9a2011-04-03 14:42:49 -070055 for log_file in log_files:
56 f = open(log_file)
57 for line in f:
58 if line_number >= start:
59 yield line
60 line_number += 1
61 f.close()
62
63
Eric Lie7c4cab2011-01-05 14:39:19 -080064 def set_start_by_regexp(self, index, regexp):
65 """Set the start of logs based on a regular expression.
66
67 @param index: line matching regexp to start at, earliest log at 0.
68 Negative numbers indicate matches since end of log.
69 """
70 regexp_compiled = re.compile(regexp)
Eric Lie7c4cab2011-01-05 14:39:19 -080071 starts = []
72 line_number = 1
Eric Li4844a9a2011-04-03 14:42:49 -070073 for line in self.read_all_logs():
Eric Lie7c4cab2011-01-05 14:39:19 -080074 if regexp_compiled.match(line):
75 starts.append(line_number)
76 line_number += 1
77 if index < -len(starts):
78 self._start_line = 1
79 elif index >= len(starts):
Eric Li4844a9a2011-04-03 14:42:49 -070080 self._start_line = line_number
Eric Lie7c4cab2011-01-05 14:39:19 -080081 else:
82 self._start_line = starts[index]
83
84
85 def set_start_by_reboot(self, index):
86 """ Set the start of logs (must be system log) based on reboot.
87
88 @param index: reboot to start at, earliest log at 0. Negative
89 numbers indicate reboots since end of log.
90 """
91 return self.set_start_by_regexp(index,
92 r'.*000\] Linux version \d')
93
94
Eric Li4844a9a2011-04-03 14:42:49 -070095 def set_start_by_current(self, relative=0):
Eric Lie7c4cab2011-01-05 14:39:19 -080096 """ Set start of logs based on current last line.
97
98 @param relative: line relative to current to start at. 1 means
99 to start the log after this line.
100 """
Eric Li4844a9a2011-04-03 14:42:49 -0700101 count = self._start_line + relative
102 for line in self.read_all_logs(start=self._start_line):
103 count += 1
104 self._start_line = count
Eric Lie7c4cab2011-01-05 14:39:19 -0800105
106
107 def get_logs(self):
108 """ Get logs since the start line.
109
110 Start line is set by set_start_* functions or
111 since the start of the file if none were called.
112
113 @return string of contents of file since start line.
114 """
Eric Li4844a9a2011-04-03 14:42:49 -0700115 logs = []
116 for line in self.read_all_logs(start=self._start_line):
117 logs.append(line)
118 return ''.join(logs)
119
Eric Lie7c4cab2011-01-05 14:39:19 -0800120
121 def can_find(self, string):
122 """ Try to find string in the logs.
123
124 @return boolean indicating if we found the string.
125 """
Jason Glasgow68cf52f2011-03-31 15:32:13 -0400126 return string in self.get_logs()
Eric Lie7c4cab2011-01-05 14:39:19 -0800127
128
Mark Koudritsky1752f9f2012-05-16 17:12:14 -0400129 def get_last_msg(self, patterns, retries=0, sleep_seconds=1):
130 """Search the logs and return the latest occurrence of a message
131 matching one of the patterns.
132
133 Args:
134 patterns: A regexp or a list of regexps to search the logs with.
135 The function returns as soon as it finds any match to one of
136 the patters, it will not search for the other patterns.
137 retries: Number of times to retry if none of the patterns were
138 found. Default is one attempt.
139 sleep_seconds: Time to sleep between retries.
140
141 Returns:
142 The last occurrence of the first matching pattern. "None" if none
143 of the patterns matched.
144 """
145
146 if not type(patterns) in (list, tuple):
147 patterns = [patterns]
148
149 for retry in xrange(retries + 1):
150 for pattern in patterns:
151 regexp_compiled = re.compile(pattern)
152 last_match = None
153 for line in self.read_all_logs():
154 if regexp_compiled.search(line):
155 last_match = line
156 if last_match:
157 return last_match
158 time.sleep(sleep_seconds)
159
160 return None
161
162
Eric Lie7c4cab2011-01-05 14:39:19 -0800163class LogRotationPauser(object):
164 """
165 Class to control when logs are rotated from either server or client.
166
167 Assumes all setting of CLEANUP_LOGS_PAUSED_FILE is done by this class
168 and that all calls to begin and end are properly
169 nested. For instance, [ a.begin(), b.begin(), b.end(), a.end() ] is
170 supported, but [ a.begin(), b.begin(), a.end(), b.end() ] is not.
171 We do support redundant calls to the same class, such as
172 [ a.begin(), a.begin(), a.end() ].
173 """
174 def __init__(self, host=None):
175 self._host = host
176 self._begun = False
177 self._is_nested = True
178
179
180 def _run(self, command, *args, **dargs):
181 if self._host:
182 return self._host.run(command, *args, **dargs).exit_status
183 else:
184 return utils.system(command, *args, **dargs)
185
186
187 def begin(self):
188 """Make sure that log rotation is disabled."""
189 if self._begun:
190 return
191 print "in begin " + str(self._begun)
192 self._is_nested = (self._run(('[ -r %s ]' %
193 CLEANUP_LOGS_PAUSED_FILE),
194 ignore_status=True) == 0)
195 print "in begin is nested: " + str(self._is_nested)
196 if self._is_nested:
197 print logging.__file__
198 logging.info('File %s was already present' %
199 CLEANUP_LOGS_PAUSED_FILE)
200 print 1
201 else:
202 self._run('touch ' + CLEANUP_LOGS_PAUSED_FILE)
203 print 2
204 self._begun = True
205
206
207 def end(self):
208 print "in end" + str(self._begun)
209 assert self._begun
210 if not self._is_nested:
211 self._run('rm -f ' + CLEANUP_LOGS_PAUSED_FILE)
212 else:
213 logging.info('Leaving existing %s file' % CLEANUP_LOGS_PAUSED_FILE)
214 self._begun = False