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