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