base_sysinfo.py revision 7a0a09c3cfe76ea8954d0c40d7282ad73f86a5a8
1import os, shutil, re, glob, subprocess, logging 2 3from autotest_lib.client.common_lib import log 4from autotest_lib.client.bin import utils, package 5 6 7_DEFAULT_COMMANDS_TO_LOG_PER_TEST = [] 8_DEFAULT_COMMANDS_TO_LOG_PER_BOOT = [ 9 "lspci -vvn", "gcc --version", "ld --version", "mount", "hostname", 10 "uptime", 11 ] 12_DEFAULT_COMMANDS_TO_LOG_BEFORE_ITERATION = [] 13_DEFAULT_COMMANDS_TO_LOG_AFTER_ITERATION = [] 14 15_DEFAULT_FILES_TO_LOG_PER_TEST = [] 16_DEFAULT_FILES_TO_LOG_PER_BOOT = [ 17 "/proc/pci", "/proc/meminfo", "/proc/slabinfo", "/proc/version", 18 "/proc/cpuinfo", "/proc/modules", "/proc/interrupts", "/proc/partitions", 19 ] 20_DEFAULT_FILES_TO_LOG_BEFORE_ITERATION = [ 21 "/proc/schedstat", "/proc/meminfo", "/proc/slabinfo", "/proc/interrupts" 22 ] 23_DEFAULT_FILES_TO_LOG_AFTER_ITERATION = [ 24 "/proc/schedstat", "/proc/meminfo", "/proc/slabinfo", "/proc/interrupts" 25 ] 26 27 28class loggable(object): 29 """ Abstract class for representing all things "loggable" by sysinfo. """ 30 def __init__(self, logf, log_in_keyval): 31 self.logf = logf 32 self.log_in_keyval = log_in_keyval 33 34 35 def readline(self, logdir): 36 path = os.path.join(logdir, self.logf) 37 if os.path.exists(path): 38 return utils.read_one_line(path) 39 else: 40 return "" 41 42 43class logfile(loggable): 44 def __init__(self, path, logf=None, log_in_keyval=False): 45 if not logf: 46 logf = os.path.basename(path) 47 super(logfile, self).__init__(logf, log_in_keyval) 48 self.path = path 49 50 51 def __repr__(self): 52 r = "sysinfo.logfile(%r, %r, %r)" 53 r %= (self.path, self.logf, self.log_in_keyval) 54 return r 55 56 57 def __eq__(self, other): 58 if isinstance(other, logfile): 59 return (self.path, self.logf) == (other.path, other.logf) 60 elif isinstance(other, loggable): 61 return False 62 return NotImplemented 63 64 65 def __ne__(self, other): 66 result = self.__eq__(other) 67 if result is NotImplemented: 68 return result 69 return not result 70 71 72 def __hash__(self): 73 return hash((self.path, self.logf)) 74 75 76 def run(self, logdir): 77 if os.path.exists(self.path): 78 shutil.copyfile(self.path, os.path.join(logdir, self.logf)) 79 80 81class command(loggable): 82 def __init__(self, cmd, logf=None, log_in_keyval=False): 83 if not logf: 84 logf = cmd.replace(" ", "_") 85 super(command, self).__init__(logf, log_in_keyval) 86 self.cmd = cmd 87 88 89 def __repr__(self): 90 r = "sysinfo.command(%r, %r, %r)" 91 r %= (self.cmd, self.logf, self.log_in_keyval) 92 return r 93 94 95 def __eq__(self, other): 96 if isinstance(other, command): 97 return (self.cmd, self.logf) == (other.cmd, other.logf) 98 elif isinstance(other, loggable): 99 return False 100 return NotImplemented 101 102 103 def __ne__(self, other): 104 result = self.__eq__(other) 105 if result is NotImplemented: 106 return result 107 return not result 108 109 110 def __hash__(self): 111 return hash((self.cmd, self.logf)) 112 113 114 def run(self, logdir): 115 stdin = open(os.devnull, "r") 116 stdout = open(os.path.join(logdir, self.logf), "w") 117 stderr = open(os.devnull, "w") 118 env = os.environ.copy() 119 if "PATH" not in env: 120 env["PATH"] = "/usr/bin:/bin" 121 subprocess.call(self.cmd, stdin=stdin, stdout=stdout, stderr=stderr, 122 shell=True, env=env) 123 for f in (stdin, stdout, stderr): 124 f.close() 125 126 127class base_sysinfo(object): 128 def __init__(self, job_resultsdir): 129 self.sysinfodir = self._get_sysinfodir(job_resultsdir) 130 131 # pull in the post-test logs to collect 132 self.test_loggables = set() 133 for cmd in _DEFAULT_COMMANDS_TO_LOG_PER_TEST: 134 self.test_loggables.add(command(cmd)) 135 for filename in _DEFAULT_FILES_TO_LOG_PER_TEST: 136 self.test_loggables.add(logfile(filename)) 137 138 # pull in the EXTRA post-boot logs to collect 139 self.boot_loggables = set() 140 for cmd in _DEFAULT_COMMANDS_TO_LOG_PER_BOOT: 141 self.boot_loggables.add(command(cmd)) 142 for filename in _DEFAULT_FILES_TO_LOG_PER_BOOT: 143 self.boot_loggables.add(logfile(filename)) 144 145 # pull in the pre test iteration logs to collect 146 self.before_iteration_loggables = set() 147 for cmd in _DEFAULT_COMMANDS_TO_LOG_BEFORE_ITERATION: 148 self.before_iteration_loggables.add( 149 command(cmd, logf=cmd.replace(" ", "_") + '.before')) 150 for fname in _DEFAULT_FILES_TO_LOG_BEFORE_ITERATION: 151 self.before_iteration_loggables.add( 152 logfile(fname, logf=os.path.basename(fname) + '.before')) 153 154 # pull in the post test iteration logs to collect 155 self.after_iteration_loggables = set() 156 for cmd in _DEFAULT_COMMANDS_TO_LOG_AFTER_ITERATION: 157 self.after_iteration_loggables.add( 158 command(cmd, logf=cmd.replace(" ", "_") + '.after')) 159 for fname in _DEFAULT_FILES_TO_LOG_AFTER_ITERATION: 160 self.after_iteration_loggables.add( 161 logfile(fname, logf=os.path.basename(fname) + '.after')) 162 163 # add in a couple of extra files and commands we want to grab 164 self.test_loggables.add(command("df -mP", logf="df")) 165 self.test_loggables.add(command("dmesg -c", logf="dmesg")) 166 self.boot_loggables.add(logfile("/proc/cmdline", 167 log_in_keyval=True)) 168 # log /proc/mounts but with custom filename since we already 169 # log the output of the "mount" command as the filename "mount" 170 self.boot_loggables.add(logfile('/proc/mounts', logf='proc_mounts')) 171 self.boot_loggables.add(command("uname -a", logf="uname", 172 log_in_keyval=True)) 173 174 175 def serialize(self): 176 return {"boot": self.boot_loggables, "test": self.test_loggables} 177 178 179 def deserialize(self, serialized): 180 self.boot_loggables = serialized["boot"] 181 self.test_loggables = serialized["test"] 182 183 184 @staticmethod 185 def _get_sysinfodir(resultsdir): 186 sysinfodir = os.path.join(resultsdir, "sysinfo") 187 if not os.path.exists(sysinfodir): 188 os.makedirs(sysinfodir) 189 return sysinfodir 190 191 192 def _get_reboot_count(self): 193 if not glob.glob(os.path.join(self.sysinfodir, "*")): 194 return -1 195 else: 196 return len(glob.glob(os.path.join(self.sysinfodir, "boot.*"))) 197 198 199 def _get_boot_subdir(self, next=False): 200 reboot_count = self._get_reboot_count() 201 if next: 202 reboot_count += 1 203 if reboot_count < 1: 204 return self.sysinfodir 205 else: 206 boot_dir = "boot.%d" % (reboot_count - 1) 207 return os.path.join(self.sysinfodir, boot_dir) 208 209 210 def _get_iteration_subdir(self, test, iteration): 211 iter_dir = "iteration.%d" % iteration 212 213 logdir = os.path.join(self._get_sysinfodir(test.outputdir), iter_dir) 214 if not os.path.exists(logdir): 215 os.mkdir(logdir) 216 return logdir 217 218 219 @log.log_and_ignore_errors("post-reboot sysinfo error:") 220 def log_per_reboot_data(self): 221 """ Logging hook called whenever a job starts, and again after 222 any reboot. """ 223 logdir = self._get_boot_subdir(next=True) 224 if not os.path.exists(logdir): 225 os.mkdir(logdir) 226 227 for log in (self.test_loggables | self.boot_loggables): 228 log.run(logdir) 229 230 # also log any installed packages 231 installed_path = os.path.join(logdir, "installed_packages") 232 installed_packages = "\n".join(package.list_all()) + "\n" 233 utils.open_write_close(installed_path, installed_packages) 234 235 236 @log.log_and_ignore_errors("pre-test sysinfo error:") 237 def log_before_each_test(self, test): 238 """ Logging hook called before a test starts. """ 239 self._installed_packages = package.list_all() 240 if os.path.exists("/var/log/messages"): 241 stat = os.stat("/var/log/messages") 242 self._messages_size = stat.st_size 243 self._messages_inode = stat.st_ino 244 245 246 @log.log_and_ignore_errors("post-test sysinfo error:") 247 def log_after_each_test(self, test): 248 """ Logging hook called after a test finishs. """ 249 test_sysinfodir = self._get_sysinfodir(test.outputdir) 250 251 # create a symlink in the test sysinfo dir to the current boot 252 reboot_dir = self._get_boot_subdir() 253 assert os.path.exists(reboot_dir) 254 symlink_dest = os.path.join(test_sysinfodir, "reboot_current") 255 symlink_src = utils.get_relative_path(reboot_dir, 256 os.path.dirname(symlink_dest)) 257 try: 258 os.symlink(symlink_src, symlink_dest) 259 except Exception, e: 260 raise Exception, '%s: whilst linking %s to %s' % (e, symlink_src, 261 symlink_dest) 262 263 # run all the standard logging commands 264 for log in self.test_loggables: 265 log.run(test_sysinfodir) 266 267 # grab any new data from /var/log/messages 268 self._log_messages(test_sysinfodir) 269 270 # log some sysinfo data into the test keyval file 271 keyval = self.log_test_keyvals(test_sysinfodir) 272 test.write_test_keyval(keyval) 273 274 # log any changes to installed packages 275 old_packages = set(self._installed_packages) 276 new_packages = set(package.list_all()) 277 added_path = os.path.join(test_sysinfodir, "added_packages") 278 added_packages = "\n".join(new_packages - old_packages) + "\n" 279 utils.open_write_close(added_path, added_packages) 280 removed_path = os.path.join(test_sysinfodir, "removed_packages") 281 removed_packages = "\n".join(old_packages - new_packages) + "\n" 282 utils.open_write_close(removed_path, removed_packages) 283 284 285 @log.log_and_ignore_errors("pre-test siteration sysinfo error:") 286 def log_before_each_iteration(self, test, iteration=None): 287 """ Logging hook called before a test iteration.""" 288 if not iteration: 289 iteration = test.iteration 290 logdir = self._get_iteration_subdir(test, iteration) 291 292 for log in self.before_iteration_loggables: 293 log.run(logdir) 294 295 296 @log.log_and_ignore_errors("post-test siteration sysinfo error:") 297 def log_after_each_iteration(self, test, iteration=None): 298 """ Logging hook called after a test iteration.""" 299 if not iteration: 300 iteration = test.iteration 301 logdir = self._get_iteration_subdir(test, iteration) 302 303 for log in self.after_iteration_loggables: 304 log.run(logdir) 305 306 307 def _log_messages(self, logdir): 308 """ Log all of the new data in /var/log/messages. """ 309 try: 310 # log all of the new data in /var/log/messages 311 bytes_to_skip = 0 312 if hasattr(self, "_messages_size"): 313 current_inode = os.stat("/var/log/messages").st_ino 314 if current_inode == self._messages_inode: 315 bytes_to_skip = self._messages_size 316 in_messages = open("/var/log/messages") 317 in_messages.seek(bytes_to_skip) 318 out_messages = open(os.path.join(logdir, "messages"), "w") 319 out_messages.write(in_messages.read()) 320 in_messages.close() 321 out_messages.close() 322 except Exception, e: 323 logging.error("/var/log/messages collection failed with %s", e) 324 325 326 @staticmethod 327 def _read_sysinfo_keyvals(loggables, logdir): 328 keyval = {} 329 for log in loggables: 330 if log.log_in_keyval: 331 keyval["sysinfo-" + log.logf] = log.readline(logdir) 332 return keyval 333 334 335 def log_test_keyvals(self, test_sysinfodir): 336 """ Logging hook called by log_after_each_test to collect keyval 337 entries to be written in the test keyval. """ 338 keyval = {} 339 340 # grab any loggables that should be in the keyval 341 keyval.update(self._read_sysinfo_keyvals( 342 self.test_loggables, test_sysinfodir)) 343 keyval.update(self._read_sysinfo_keyvals( 344 self.boot_loggables, 345 os.path.join(test_sysinfodir, "reboot_current"))) 346 347 # remove hostname from uname info 348 # Linux lpt36 2.6.18-smp-230.1 #1 [4069269] SMP Fri Oct 24 11:30:... 349 if "sysinfo-uname" in keyval: 350 kernel_vers = " ".join(keyval["sysinfo-uname"].split()[2:]) 351 keyval["sysinfo-uname"] = kernel_vers 352 353 # grab the total avail memory, not used by sys tables 354 path = os.path.join(test_sysinfodir, "reboot_current", "meminfo") 355 if os.path.exists(path): 356 mem_data = open(path).read() 357 match = re.search(r"^MemTotal:\s+(\d+) kB$", mem_data, 358 re.MULTILINE) 359 if match: 360 keyval["sysinfo-memtotal-in-kb"] = match.group(1) 361 362 # guess the system's total physical memory, including sys tables 363 keyval["sysinfo-phys-mbytes"] = utils.rounded_memtotal()//1024 364 365 # return what we collected 366 return keyval 367