docs: Fix README link
[piglit.git] / framework / dmesg.py
blob37fbdad7b2876b8f869baf2c7fe3148af3afb851
1 # coding=utf-8
2 # Copyright (c) 2013-2016, 2019 Intel Corporation
4 # Permission is hereby granted, free of charge, to any person obtaining a
5 # copy of this software and associated documentation files (the "Software"),
6 # to deal in the Software without restriction, including without limitation
7 # the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 # and/or sell copies of the Software, and to permit persons to whom the
9 # Software is furnished to do so, subject to the following conditions:
11 # The above copyright notice and this permission notice (including the next
12 # paragraph) shall be included in all copies or substantial portions of the
13 # Software.
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 # IN THE SOFTWARE.
23 """ Module implementing classes for reading posix dmesg
25 Currently this module only has the default DummyDmesg, and a LinuxDmesg.
26 The method used on Linux requires that timestamps are enabled, and no other
27 posix system has timestamps.
29 On OSX and *BSD one would likely want to implement a system that reads the
30 sysloger, since timestamps can be added by the sysloger, and are not inserted
31 by dmesg.
33 Most users do not want to call a specific dmesg implementation themselves,
34 they will want to use the get_dmesg() helper, which provides the proper
35 dmesg implementation for their OS.
37 """
39 import abc
40 import gzip
41 import re
42 import subprocess
43 import sys
44 import warnings
46 from framework import exceptions
48 __all__ = [
49 'BaseDmesg',
50 'DummyDmesg',
51 'LinuxDmesg',
52 'get_dmesg',
56 class BaseDmesg(metaclass=abc.ABCMeta):
57 """ Abstract base class for Dmesg derived objects
59 This provides the bases of the constructor, and most subclasses should call
60 super() in __init__(). It provides a concrete implementation of the
61 update_result() method, which should be suitible for all subclasses.
63 The update_dmesg() method is the primary method that each subclass needs to
64 override, as this method is used to to actually read the dmesg ringbuffer,
65 and the command used, and the options given are OS dependent.
67 This class is not thread safe, because it does not black between the start
68 of the test and the reading of dmesg, which means that if two tests run at
69 the same time, and test A creates an entri in dmesg, but test B finishes
70 first, test B will be marked as having the dmesg error.
72 """
73 @abc.abstractmethod
74 def __init__(self):
75 # A list containing all messages since the last time dmesg was read.
76 # This is used by update result, and then emptied
77 self._new_messages = []
79 # If this is set it should be an re.compile() object, which matches
80 # entries that are to be considered failures. If an entry does not
81 # match the regex it will be ignored. By default (None) all entries are
82 # considered
83 self.regex = None
85 # Populate self.dmesg initially, otherwise the first test will always
86 # be full of false positives.
87 self.update_dmesg()
89 @abc.abstractmethod
90 def update_dmesg(self):
91 """ Update _new_messages list
93 This method should read dmesg, determine which entries are new since
94 the last read of dmesg, and update self._new_messages with those
95 entries.
97 """
98 pass
100 def update_result(self, result):
101 """ Takes a TestResult object and updates it with dmesg statuses
103 If dmesg is enabled, and if dmesg has been updated, then replace pass
104 with dmesg-warn and warn and fail with dmesg-fail. If dmesg has not
105 been updated, it will return the original result passed in.
107 Arguments:
108 result -- A TestResult instance
111 def replace(res):
112 """ helper to replace statuses with the new dmesg status
114 Replaces pass with dmesg-warn, and warn and fail with dmesg-fail,
115 otherwise returns the input
118 return {
119 "pass": "dmesg-warn",
120 "warn": "dmesg-fail",
121 "fail": "dmesg-fail"
122 }.get(res, res)
124 # Get a new snapshot of dmesg
125 self.update_dmesg()
127 # if update_dmesg() found new entries replace the results of the test
128 # and subtests
129 if self._new_messages:
131 if self.regex:
132 for line in self._new_messages:
133 if self.regex.search(line):
134 break
135 else:
136 return result
138 result.result = replace(result.result)
140 # Replace the results of any subtests
141 for key, value in result.subtests.items():
142 result.subtests[key] = replace(value)
144 # Add the dmesg values to the result
145 result.dmesg = "\n".join(self._new_messages)
147 return result
149 def __repr__(self):
150 return 'BaseDmesg()'
153 class LinuxDmesg(BaseDmesg):
154 """ Read dmesg on posix systems
156 This reads the dmesg ring buffer, stores the contents of the buffer, and
157 then compares it whenever called, returning the difference between the
158 calls. It requires timestamps to be enabled.
161 DMESG_COMMAND = ['dmesg', '--level', 'emerg,alert,crit,err,warn,notice']
163 def __init__(self):
164 """ Create a dmesg instance """
165 # _last_message store the contents of the last entry in dmesg the last
166 # time it was read. Because dmesg is a ringbuffer old entries can fall
167 # off the end if it becomes too full. To track new entries search for
168 # this entry and take any entries that were added after it. This works
169 # on Linux because each entry is guaranteed unique by timestamps.
170 self._last_message = None
171 super(LinuxDmesg, self).__init__()
173 # First check to see if we can find the live kernel config.
174 try:
175 with gzip.open(b"/proc/config.gz", 'r') as f:
176 for line in f:
177 if line.startswith(b"CONFIG_PRINTK_TIME=y"):
178 return
179 # If there is a malformed or missing file, just ignore it and try the
180 # regex match (which may not exist if dmesg ringbuffer was cleared).
181 except Exception:
182 pass
184 if not self._last_message:
185 # We need to ensure that there is something in dmesg to check
186 # timestamps.
187 warnings.warn("Cannot check dmesg for timestamp support. If you "
188 "do not have timestamps enabled in your kernel you "
189 "get incomplete dmesg captures", RuntimeWarning)
190 elif not re.match(r'\[\s*\d+\.\d+\]', self._last_message):
191 # Do an initial check to ensure that dmesg has timestamps, we need
192 # timestamps to work correctly. A proper linux dmesg timestamp
193 # looks like this: [ 0.00000]
194 raise exceptions.PiglitFatalError(
195 "Your kernel does not seem to support timestamps, which "
196 "are required by the --dmesg option")
198 def update_dmesg(self):
199 """ Call dmesg using subprocess.check_output
201 Get the contents of dmesg, then calculate new messages, finally set
202 self.dmesg equal to the just read contents of dmesg.
205 dmesg = subprocess.check_output(self.DMESG_COMMAND).decode('utf-8')
206 dmesg = dmesg.strip().splitlines()
208 # Find all new entries, do this by slicing the list of dmesg to only
209 # returns elements after the last element stored. If there are not
210 # matches a value error is raised, that means all of dmesg is new
211 l = 0
212 for index, item in enumerate(reversed(dmesg)):
213 if item == self._last_message:
214 l = len(dmesg) - index # don't include the matched element
215 break
216 self._new_messages = dmesg[l:]
218 # Attempt to store the last element of dmesg, unless there was no dmesg
219 self._last_message = dmesg[-1] if dmesg else None
221 def __repr__(self):
222 return 'LinuxDmesg()'
225 class DummyDmesg(BaseDmesg):
226 """ An dummy class for dmesg on non unix-like systems
228 This implements a dummy version of the LinuxDmesg, and can be used anytime
229 it is not desirable to actually read dmesg, such as non-posix systems, or
230 when the contents of dmesg don't matter.
233 DMESG_COMMAND = []
235 def __init__(self):
236 pass
238 def update_dmesg(self):
239 """ Dummy version of update_dmesg """
240 pass
242 def update_result(self, result):
243 """ Dummy version of update_result """
244 return result
246 def __repr__(self):
247 return 'DummyDmesg()'
250 def get_dmesg(not_dummy=True):
251 """ Return a Dmesg type instance
253 Normally this does a system check, and returns the type that proper for
254 your system. However, if Dummy is True then it will always return a
255 DummyDmesg instance.
258 if sys.platform.startswith('linux') and not_dummy:
259 return LinuxDmesg()
260 return DummyDmesg()