1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Liimport abc 3*9c5db199SXin Liimport glob 4*9c5db199SXin Liimport logging 5*9c5db199SXin Liimport os 6*9c5db199SXin Liimport re 7*9c5db199SXin Liimport shutil 8*9c5db199SXin Liimport subprocess 9*9c5db199SXin Li 10*9c5db199SXin Lifrom distutils import dir_util 11*9c5db199SXin Li 12*9c5db199SXin Lifrom autotest_lib.client.common_lib import log 13*9c5db199SXin Lifrom autotest_lib.client.cros import constants 14*9c5db199SXin Lifrom autotest_lib.client.bin import utils, package 15*9c5db199SXin Li 16*9c5db199SXin Li_DEFAULT_COMMANDS_TO_LOG_PER_TEST = [] 17*9c5db199SXin Li_DEFAULT_COMMANDS_TO_LOG_PER_BOOT = [ 18*9c5db199SXin Li 'lspci -vvnn', 19*9c5db199SXin Li 'gcc --version', 20*9c5db199SXin Li 'ld --version', 21*9c5db199SXin Li 'mount', 22*9c5db199SXin Li 'hostname', 23*9c5db199SXin Li 'uptime', 24*9c5db199SXin Li # for Downloadable Content (DLC) 25*9c5db199SXin Li 'losetup', 26*9c5db199SXin Li 'dlcservice_util --list', 27*9c5db199SXin Li] 28*9c5db199SXin Li_DEFAULT_COMMANDS_TO_LOG_BEFORE_ITERATION = [] 29*9c5db199SXin Li_DEFAULT_COMMANDS_TO_LOG_AFTER_ITERATION = [] 30*9c5db199SXin Li 31*9c5db199SXin Li_DEFAULT_FILES_TO_LOG_PER_TEST = [] 32*9c5db199SXin Li_DEFAULT_FILES_TO_LOG_PER_BOOT = [ 33*9c5db199SXin Li '/proc/pci', 34*9c5db199SXin Li '/proc/meminfo', 35*9c5db199SXin Li '/proc/slabinfo', 36*9c5db199SXin Li '/proc/version', 37*9c5db199SXin Li '/proc/cpuinfo', 38*9c5db199SXin Li '/proc/modules', 39*9c5db199SXin Li '/proc/interrupts', 40*9c5db199SXin Li '/proc/partitions', 41*9c5db199SXin Li '/sys/firmware/log', 42*9c5db199SXin Li '/var/log/bios_info.txt', 43*9c5db199SXin Li '/var/log/messages', 44*9c5db199SXin Li '/var/log/storage_info.txt', 45*9c5db199SXin Li] + list(constants.LOG_PSTORE_DIRS) 46*9c5db199SXin Li_DEFAULT_FILES_TO_LOG_BEFORE_ITERATION = [ 47*9c5db199SXin Li '/proc/diskstats', 48*9c5db199SXin Li '/proc/schedstat', 49*9c5db199SXin Li '/proc/meminfo', 50*9c5db199SXin Li '/proc/slabinfo', 51*9c5db199SXin Li '/proc/interrupts' 52*9c5db199SXin Li] 53*9c5db199SXin Li_DEFAULT_FILES_TO_LOG_AFTER_ITERATION = [ 54*9c5db199SXin Li '/proc/diskstats', 55*9c5db199SXin Li '/proc/schedstat', 56*9c5db199SXin Li '/proc/meminfo', 57*9c5db199SXin Li '/proc/slabinfo', 58*9c5db199SXin Li '/proc/interrupts' 59*9c5db199SXin Li] 60*9c5db199SXin Li 61*9c5db199SXin Li 62*9c5db199SXin Liclass loggable(object): 63*9c5db199SXin Li """ Abstract class for representing all things "loggable" by sysinfo. """ 64*9c5db199SXin Li 65*9c5db199SXin Li def __init__(self, logf, log_in_keyval): 66*9c5db199SXin Li self.logf = logf 67*9c5db199SXin Li self.log_in_keyval = log_in_keyval 68*9c5db199SXin Li 69*9c5db199SXin Li def readline(self, logdir): 70*9c5db199SXin Li """Reads one line from the log. 71*9c5db199SXin Li 72*9c5db199SXin Li @param logdir: The log directory. 73*9c5db199SXin Li @return A line from the log, or the empty string if the log doesn't 74*9c5db199SXin Li exist. 75*9c5db199SXin Li """ 76*9c5db199SXin Li path = os.path.join(logdir, self.logf) 77*9c5db199SXin Li if os.path.exists(path): 78*9c5db199SXin Li return utils.read_one_line(path) 79*9c5db199SXin Li else: 80*9c5db199SXin Li return "" 81*9c5db199SXin Li 82*9c5db199SXin Li @abc.abstractmethod 83*9c5db199SXin Li def run(self, logdir): 84*9c5db199SXin Li """Executes this loggable creating output in logdir. 85*9c5db199SXin Li 86*9c5db199SXin Li @param logdir: The output directory. 87*9c5db199SXin Li """ 88*9c5db199SXin Li raise NotImplementedError() 89*9c5db199SXin Li 90*9c5db199SXin Li 91*9c5db199SXin Liclass logfile(loggable): 92*9c5db199SXin Li """Represents a log file.""" 93*9c5db199SXin Li def __init__(self, path, logf=None, log_in_keyval=False): 94*9c5db199SXin Li if not logf: 95*9c5db199SXin Li logf = os.path.basename(path) 96*9c5db199SXin Li super(logfile, self).__init__(logf, log_in_keyval) 97*9c5db199SXin Li self.path = path 98*9c5db199SXin Li 99*9c5db199SXin Li 100*9c5db199SXin Li def __repr__(self): 101*9c5db199SXin Li r = "sysinfo.logfile(%r, %r, %r)" 102*9c5db199SXin Li r %= (self.path, self.logf, self.log_in_keyval) 103*9c5db199SXin Li return r 104*9c5db199SXin Li 105*9c5db199SXin Li 106*9c5db199SXin Li def __eq__(self, other): 107*9c5db199SXin Li if isinstance(other, logfile): 108*9c5db199SXin Li return (self.path, self.logf) == (other.path, other.logf) 109*9c5db199SXin Li elif isinstance(other, loggable): 110*9c5db199SXin Li return False 111*9c5db199SXin Li return NotImplemented 112*9c5db199SXin Li 113*9c5db199SXin Li 114*9c5db199SXin Li def __ne__(self, other): 115*9c5db199SXin Li result = self.__eq__(other) 116*9c5db199SXin Li if result is NotImplemented: 117*9c5db199SXin Li return result 118*9c5db199SXin Li return not result 119*9c5db199SXin Li 120*9c5db199SXin Li 121*9c5db199SXin Li def __hash__(self): 122*9c5db199SXin Li return hash((self.path, self.logf)) 123*9c5db199SXin Li 124*9c5db199SXin Li 125*9c5db199SXin Li def run(self, logdir): 126*9c5db199SXin Li """Copies the log file or directory to the specified directory. 127*9c5db199SXin Li 128*9c5db199SXin Li @param logdir: The log directory. 129*9c5db199SXin Li """ 130*9c5db199SXin Li if os.path.exists(self.path): 131*9c5db199SXin Li if os.path.isdir(self.path): 132*9c5db199SXin Li dst = os.path.join(logdir, self.logf, self.path.lstrip('/')) 133*9c5db199SXin Li dir_util.copy_tree(self.path, dst) 134*9c5db199SXin Li else: 135*9c5db199SXin Li dst = os.path.join(logdir, self.logf) 136*9c5db199SXin Li shutil.copyfile(self.path, dst) 137*9c5db199SXin Li logging.debug('Loggable saves logs to %s', dst) 138*9c5db199SXin Li 139*9c5db199SXin Li 140*9c5db199SXin Liclass command(loggable): 141*9c5db199SXin Li """Represents a command.""" 142*9c5db199SXin Li def __init__(self, cmd, logf=None, log_in_keyval=False, compress_log=False): 143*9c5db199SXin Li if not logf: 144*9c5db199SXin Li logf = cmd.replace(" ", "_") 145*9c5db199SXin Li super(command, self).__init__(logf, log_in_keyval) 146*9c5db199SXin Li self.cmd = cmd 147*9c5db199SXin Li if compress_log: 148*9c5db199SXin Li self.cmd += ' | gzip -9' 149*9c5db199SXin Li self.logf += '.gz' 150*9c5db199SXin Li 151*9c5db199SXin Li 152*9c5db199SXin Li def __repr__(self): 153*9c5db199SXin Li r = "sysinfo.command(%r, %r, %r)" 154*9c5db199SXin Li r %= (self.cmd, self.logf, self.log_in_keyval) 155*9c5db199SXin Li return r 156*9c5db199SXin Li 157*9c5db199SXin Li 158*9c5db199SXin Li def __eq__(self, other): 159*9c5db199SXin Li if isinstance(other, command): 160*9c5db199SXin Li return (self.cmd, self.logf) == (other.cmd, other.logf) 161*9c5db199SXin Li elif isinstance(other, loggable): 162*9c5db199SXin Li return False 163*9c5db199SXin Li return NotImplemented 164*9c5db199SXin Li 165*9c5db199SXin Li 166*9c5db199SXin Li def __ne__(self, other): 167*9c5db199SXin Li result = self.__eq__(other) 168*9c5db199SXin Li if result is NotImplemented: 169*9c5db199SXin Li return result 170*9c5db199SXin Li return not result 171*9c5db199SXin Li 172*9c5db199SXin Li 173*9c5db199SXin Li def __hash__(self): 174*9c5db199SXin Li return hash((self.cmd, self.logf)) 175*9c5db199SXin Li 176*9c5db199SXin Li 177*9c5db199SXin Li def run(self, logdir): 178*9c5db199SXin Li """Runs the command. 179*9c5db199SXin Li 180*9c5db199SXin Li @param logdir: The log directory. 181*9c5db199SXin Li """ 182*9c5db199SXin Li env = os.environ.copy() 183*9c5db199SXin Li if "PATH" not in env: 184*9c5db199SXin Li env["PATH"] = "/usr/bin:/bin" 185*9c5db199SXin Li logf_path = os.path.join(logdir, self.logf) 186*9c5db199SXin Li stdin = open(os.devnull, "r") 187*9c5db199SXin Li stderr = open(os.devnull, "w") 188*9c5db199SXin Li stdout = open(logf_path, "w") 189*9c5db199SXin Li try: 190*9c5db199SXin Li logging.debug('Loggable runs cmd: %s', self.cmd) 191*9c5db199SXin Li subprocess.call(self.cmd, 192*9c5db199SXin Li stdin=stdin, 193*9c5db199SXin Li stdout=stdout, 194*9c5db199SXin Li stderr=stderr, 195*9c5db199SXin Li shell=True, 196*9c5db199SXin Li env=env) 197*9c5db199SXin Li finally: 198*9c5db199SXin Li for f in (stdin, stdout, stderr): 199*9c5db199SXin Li f.close() 200*9c5db199SXin Li 201*9c5db199SXin Li 202*9c5db199SXin Liclass base_sysinfo(object): 203*9c5db199SXin Li """Represents system info.""" 204*9c5db199SXin Li def __init__(self, job_resultsdir): 205*9c5db199SXin Li self.sysinfodir = self._get_sysinfodir(job_resultsdir) 206*9c5db199SXin Li 207*9c5db199SXin Li # pull in the post-test logs to collect 208*9c5db199SXin Li self.test_loggables = set() 209*9c5db199SXin Li for cmd in _DEFAULT_COMMANDS_TO_LOG_PER_TEST: 210*9c5db199SXin Li self.test_loggables.add(command(cmd)) 211*9c5db199SXin Li for filename in _DEFAULT_FILES_TO_LOG_PER_TEST: 212*9c5db199SXin Li self.test_loggables.add(logfile(filename)) 213*9c5db199SXin Li 214*9c5db199SXin Li # pull in the EXTRA post-boot logs to collect 215*9c5db199SXin Li self.boot_loggables = set() 216*9c5db199SXin Li for cmd in _DEFAULT_COMMANDS_TO_LOG_PER_BOOT: 217*9c5db199SXin Li self.boot_loggables.add(command(cmd)) 218*9c5db199SXin Li for filename in _DEFAULT_FILES_TO_LOG_PER_BOOT: 219*9c5db199SXin Li self.boot_loggables.add(logfile(filename)) 220*9c5db199SXin Li 221*9c5db199SXin Li # pull in the pre test iteration logs to collect 222*9c5db199SXin Li self.before_iteration_loggables = set() 223*9c5db199SXin Li for cmd in _DEFAULT_COMMANDS_TO_LOG_BEFORE_ITERATION: 224*9c5db199SXin Li self.before_iteration_loggables.add( 225*9c5db199SXin Li command(cmd, logf=cmd.replace(" ", "_") + '.before')) 226*9c5db199SXin Li for fname in _DEFAULT_FILES_TO_LOG_BEFORE_ITERATION: 227*9c5db199SXin Li self.before_iteration_loggables.add( 228*9c5db199SXin Li logfile(fname, logf=os.path.basename(fname) + '.before')) 229*9c5db199SXin Li 230*9c5db199SXin Li # pull in the post test iteration logs to collect 231*9c5db199SXin Li self.after_iteration_loggables = set() 232*9c5db199SXin Li for cmd in _DEFAULT_COMMANDS_TO_LOG_AFTER_ITERATION: 233*9c5db199SXin Li self.after_iteration_loggables.add( 234*9c5db199SXin Li command(cmd, logf=cmd.replace(" ", "_") + '.after')) 235*9c5db199SXin Li for fname in _DEFAULT_FILES_TO_LOG_AFTER_ITERATION: 236*9c5db199SXin Li self.after_iteration_loggables.add( 237*9c5db199SXin Li logfile(fname, logf=os.path.basename(fname) + '.after')) 238*9c5db199SXin Li 239*9c5db199SXin Li # add in a couple of extra files and commands we want to grab 240*9c5db199SXin Li self.test_loggables.add(command("df -mP", logf="df")) 241*9c5db199SXin Li # We compress the dmesg because it can get large when kernels are 242*9c5db199SXin Li # configured with a large buffer and some tests trigger OOMs or 243*9c5db199SXin Li # other large "spam" that fill it up... 244*9c5db199SXin Li self.test_loggables.add(command('dmesg -c', 245*9c5db199SXin Li logf='dmesg', 246*9c5db199SXin Li compress_log=True)) 247*9c5db199SXin Li self.boot_loggables.add(logfile('/proc/cmdline', log_in_keyval=True)) 248*9c5db199SXin Li # log /proc/mounts but with custom filename since we already 249*9c5db199SXin Li # log the output of the "mount" command as the filename "mount" 250*9c5db199SXin Li self.boot_loggables.add(logfile('/proc/mounts', logf='proc_mounts')) 251*9c5db199SXin Li self.boot_loggables.add(command('uname -a', 252*9c5db199SXin Li logf='uname', 253*9c5db199SXin Li log_in_keyval=True)) 254*9c5db199SXin Li 255*9c5db199SXin Li # Log cpufreq parameters 256*9c5db199SXin Li self.boot_loggables.add( 257*9c5db199SXin Li command('cat /sys/bus/cpu/devices/cpu*/cpufreq/scaling_driver | sort -u', 258*9c5db199SXin Li logf='scaling-driver', 259*9c5db199SXin Li log_in_keyval=True)) 260*9c5db199SXin Li self.boot_loggables.add( 261*9c5db199SXin Li command('cat /sys/bus/cpu/devices/cpu*/cpufreq/scaling_governor | sort -u', 262*9c5db199SXin Li logf='scaling-governor', 263*9c5db199SXin Li log_in_keyval=True)) 264*9c5db199SXin Li # Will only get logged when using the ondemand governor 265*9c5db199SXin Li self.boot_loggables.add( 266*9c5db199SXin Li logfile('/sys/devices/system/cpu/cpufreq/ondemand/powersave_bias', 267*9c5db199SXin Li logf='scaling-governor-ondemand-powersave-bias', 268*9c5db199SXin Li log_in_keyval=True)) 269*9c5db199SXin Li 270*9c5db199SXin Li # log contents of DLC directories with meaningful filenames 271*9c5db199SXin Li self.boot_loggables.add(command('tree /var/cache/dlc', 272*9c5db199SXin Li logf='dlc_images')) 273*9c5db199SXin Li self.boot_loggables.add(command( 274*9c5db199SXin Li 'tree /mnt/stateful_partition/var_overlay/cache/dlc-images', 275*9c5db199SXin Li logf='dlc_preloaded_images')) 276*9c5db199SXin Li 277*9c5db199SXin Li self._installed_packages = [] 278*9c5db199SXin Li self._journal_cursor = None 279*9c5db199SXin Li self._system_log_cursor = None 280*9c5db199SXin Li 281*9c5db199SXin Li 282*9c5db199SXin Li def serialize(self): 283*9c5db199SXin Li """Returns parameters in a dictionary.""" 284*9c5db199SXin Li return {"boot": self.boot_loggables, "test": self.test_loggables} 285*9c5db199SXin Li 286*9c5db199SXin Li 287*9c5db199SXin Li def deserialize(self, serialized): 288*9c5db199SXin Li """Stores parameters from a dictionary into instance variables. 289*9c5db199SXin Li 290*9c5db199SXin Li @param serialized: A dictionary containing parameters to store as 291*9c5db199SXin Li instance variables. 292*9c5db199SXin Li """ 293*9c5db199SXin Li self.boot_loggables = serialized["boot"] 294*9c5db199SXin Li self.test_loggables = serialized["test"] 295*9c5db199SXin Li 296*9c5db199SXin Li 297*9c5db199SXin Li @staticmethod 298*9c5db199SXin Li def _get_sysinfodir(resultsdir): 299*9c5db199SXin Li sysinfodir = os.path.join(resultsdir, "sysinfo") 300*9c5db199SXin Li if not os.path.exists(sysinfodir): 301*9c5db199SXin Li os.makedirs(sysinfodir) 302*9c5db199SXin Li return sysinfodir 303*9c5db199SXin Li 304*9c5db199SXin Li 305*9c5db199SXin Li def _get_reboot_count(self): 306*9c5db199SXin Li if not glob.glob(os.path.join(self.sysinfodir, "*")): 307*9c5db199SXin Li return -1 308*9c5db199SXin Li else: 309*9c5db199SXin Li return len(glob.glob(os.path.join(self.sysinfodir, "boot.*"))) 310*9c5db199SXin Li 311*9c5db199SXin Li 312*9c5db199SXin Li def _get_boot_subdir(self, next=False): 313*9c5db199SXin Li reboot_count = self._get_reboot_count() 314*9c5db199SXin Li if next: 315*9c5db199SXin Li reboot_count += 1 316*9c5db199SXin Li if reboot_count < 1: 317*9c5db199SXin Li return self.sysinfodir 318*9c5db199SXin Li else: 319*9c5db199SXin Li boot_dir = "boot.%d" % (reboot_count - 1) 320*9c5db199SXin Li return os.path.join(self.sysinfodir, boot_dir) 321*9c5db199SXin Li 322*9c5db199SXin Li 323*9c5db199SXin Li def _get_iteration_subdir(self, test, iteration): 324*9c5db199SXin Li iter_dir = "iteration.%d" % iteration 325*9c5db199SXin Li 326*9c5db199SXin Li logdir = os.path.join(self._get_sysinfodir(test.outputdir), iter_dir) 327*9c5db199SXin Li if not os.path.exists(logdir): 328*9c5db199SXin Li os.mkdir(logdir) 329*9c5db199SXin Li return logdir 330*9c5db199SXin Li 331*9c5db199SXin Li 332*9c5db199SXin Li @log.log_and_ignore_errors("post-reboot sysinfo error:") 333*9c5db199SXin Li def log_per_reboot_data(self): 334*9c5db199SXin Li """Logging hook called when a job starts, and again after any reboot.""" 335*9c5db199SXin Li logdir = self._get_boot_subdir(next=True) 336*9c5db199SXin Li if not os.path.exists(logdir): 337*9c5db199SXin Li os.mkdir(logdir) 338*9c5db199SXin Li 339*9c5db199SXin Li _run_loggables_ignoring_errors( 340*9c5db199SXin Li self.test_loggables | self.boot_loggables, 341*9c5db199SXin Li logdir) 342*9c5db199SXin Li 343*9c5db199SXin Li # also log any installed packages 344*9c5db199SXin Li installed_path = os.path.join(logdir, "installed_packages") 345*9c5db199SXin Li installed_packages = "\n".join(package.list_all()) + "\n" 346*9c5db199SXin Li utils.open_write_close(installed_path, installed_packages) 347*9c5db199SXin Li 348*9c5db199SXin Li 349*9c5db199SXin Li @log.log_and_ignore_errors("pre-test sysinfo error:") 350*9c5db199SXin Li def log_before_each_test(self, test): 351*9c5db199SXin Li """Logging hook called before a test starts. 352*9c5db199SXin Li 353*9c5db199SXin Li @param test: A test object. 354*9c5db199SXin Li """ 355*9c5db199SXin Li self._installed_packages = package.list_all() 356*9c5db199SXin Li if os.path.exists("/var/log/messages"): 357*9c5db199SXin Li stat = os.stat("/var/log/messages") 358*9c5db199SXin Li self._messages_size = stat.st_size 359*9c5db199SXin Li self._messages_inode = stat.st_ino 360*9c5db199SXin Li 361*9c5db199SXin Li self._system_log_cursor = get_system_log_cursor() 362*9c5db199SXin Li if not self._system_log_cursor: 363*9c5db199SXin Li # TODO(yoshiki): remove journald related code: crbug.com/1066706 364*9c5db199SXin Li self._journal_cursor = get_journal_cursor() 365*9c5db199SXin Li 366*9c5db199SXin Li # We want to only log the entries from this point on, not everything. 367*9c5db199SXin Li # When you do not filter from the cursor on the journal.gz can be 368*9c5db199SXin Li # extremely large and take a very long amount of time to compress. 369*9c5db199SXin Li if self._system_log_cursor: 370*9c5db199SXin Li self.test_loggables.add(command(( 371*9c5db199SXin Li '/usr/sbin/croslog --output=export --cursor="{}"'.format(self._system_log_cursor)), 372*9c5db199SXin Li logf='unified-log', 373*9c5db199SXin Li compress_log=True)) 374*9c5db199SXin Li elif self._journal_cursor: 375*9c5db199SXin Li # TODO(yoshiki): remove journald related code: crbug.com/1066706 376*9c5db199SXin Li self.test_loggables.add(command(( 377*9c5db199SXin Li 'journalctl -o export -c "{}"'.format(self._journal_cursor)), 378*9c5db199SXin Li logf='journal', 379*9c5db199SXin Li compress_log=True)) 380*9c5db199SXin Li 381*9c5db199SXin Li 382*9c5db199SXin Li # log some sysinfo data into the test keyval file in case system crash. 383*9c5db199SXin Li test_sysinfodir = self._get_sysinfodir(test.outputdir) 384*9c5db199SXin Li keyval = self.log_test_keyvals(test_sysinfodir) 385*9c5db199SXin Li test.write_test_keyval(keyval) 386*9c5db199SXin Li 387*9c5db199SXin Li @log.log_and_ignore_errors("post-test sysinfo error:") 388*9c5db199SXin Li def log_after_each_test(self, test): 389*9c5db199SXin Li """Logging hook called after a test finishs. 390*9c5db199SXin Li 391*9c5db199SXin Li @param test: A test object. 392*9c5db199SXin Li """ 393*9c5db199SXin Li test_sysinfodir = self._get_sysinfodir(test.outputdir) 394*9c5db199SXin Li 395*9c5db199SXin Li # create a symlink in the test sysinfo dir to the current boot 396*9c5db199SXin Li reboot_dir = self._get_boot_subdir() 397*9c5db199SXin Li assert os.path.exists(reboot_dir) 398*9c5db199SXin Li symlink_dest = os.path.join(test_sysinfodir, "reboot_current") 399*9c5db199SXin Li symlink_src = utils.get_relative_path(reboot_dir, 400*9c5db199SXin Li os.path.dirname(symlink_dest)) 401*9c5db199SXin Li try: 402*9c5db199SXin Li os.symlink(symlink_src, symlink_dest) 403*9c5db199SXin Li except Exception as e: 404*9c5db199SXin Li raise Exception('%s: whilst linking %s to %s' % (e, symlink_src, 405*9c5db199SXin Li symlink_dest)) 406*9c5db199SXin Li 407*9c5db199SXin Li # run all the standard logging commands 408*9c5db199SXin Li _run_loggables_ignoring_errors(self.test_loggables, test_sysinfodir) 409*9c5db199SXin Li 410*9c5db199SXin Li # grab any new data from /var/log/messages 411*9c5db199SXin Li self._log_messages(test_sysinfodir) 412*9c5db199SXin Li 413*9c5db199SXin Li # log some sysinfo data into the test keyval file 414*9c5db199SXin Li keyval = self.log_test_keyvals(test_sysinfodir) 415*9c5db199SXin Li test.write_test_keyval(keyval) 416*9c5db199SXin Li 417*9c5db199SXin Li # log any changes to installed packages 418*9c5db199SXin Li old_packages = set(self._installed_packages) 419*9c5db199SXin Li new_packages = set(package.list_all()) 420*9c5db199SXin Li added_path = os.path.join(test_sysinfodir, "added_packages") 421*9c5db199SXin Li added_packages = "\n".join(new_packages - old_packages) + "\n" 422*9c5db199SXin Li utils.open_write_close(added_path, added_packages) 423*9c5db199SXin Li removed_path = os.path.join(test_sysinfodir, "removed_packages") 424*9c5db199SXin Li removed_packages = "\n".join(old_packages - new_packages) + "\n" 425*9c5db199SXin Li utils.open_write_close(removed_path, removed_packages) 426*9c5db199SXin Li 427*9c5db199SXin Li 428*9c5db199SXin Li @log.log_and_ignore_errors("pre-test siteration sysinfo error:") 429*9c5db199SXin Li def log_before_each_iteration(self, test, iteration=None): 430*9c5db199SXin Li """Logging hook called before a test iteration. 431*9c5db199SXin Li 432*9c5db199SXin Li @param test: A test object. 433*9c5db199SXin Li @param iteration: A test iteration. 434*9c5db199SXin Li """ 435*9c5db199SXin Li if not iteration: 436*9c5db199SXin Li iteration = test.iteration 437*9c5db199SXin Li logdir = self._get_iteration_subdir(test, iteration) 438*9c5db199SXin Li _run_loggables_ignoring_errors(self.before_iteration_loggables, logdir) 439*9c5db199SXin Li # Start each log with the board name for orientation. 440*9c5db199SXin Li board = utils.get_board_with_frequency_and_memory() 441*9c5db199SXin Li logging.info('ChromeOS BOARD = %s', board) 442*9c5db199SXin Li # Leave some autotest bread crumbs in the system logs. 443*9c5db199SXin Li utils.system('logger "autotest starting iteration %s on %s"' % (logdir, 444*9c5db199SXin Li board), 445*9c5db199SXin Li ignore_status=True) 446*9c5db199SXin Li 447*9c5db199SXin Li 448*9c5db199SXin Li @log.log_and_ignore_errors("post-test siteration sysinfo error:") 449*9c5db199SXin Li def log_after_each_iteration(self, test, iteration=None): 450*9c5db199SXin Li """Logging hook called after a test iteration. 451*9c5db199SXin Li 452*9c5db199SXin Li @param test: A test object. 453*9c5db199SXin Li @param iteration: A test iteration. 454*9c5db199SXin Li """ 455*9c5db199SXin Li if not iteration: 456*9c5db199SXin Li iteration = test.iteration 457*9c5db199SXin Li logdir = self._get_iteration_subdir(test, iteration) 458*9c5db199SXin Li _run_loggables_ignoring_errors(self.after_iteration_loggables, logdir) 459*9c5db199SXin Li utils.system('logger "autotest finished iteration %s"' % logdir, 460*9c5db199SXin Li ignore_status=True) 461*9c5db199SXin Li 462*9c5db199SXin Li 463*9c5db199SXin Li def _log_messages(self, logdir): 464*9c5db199SXin Li """Log all of the new data in /var/log/messages.""" 465*9c5db199SXin Li try: 466*9c5db199SXin Li # log all of the new data in /var/log/messages 467*9c5db199SXin Li bytes_to_skip = 0 468*9c5db199SXin Li if hasattr(self, "_messages_size"): 469*9c5db199SXin Li current_inode = os.stat("/var/log/messages").st_ino 470*9c5db199SXin Li if current_inode == self._messages_inode: 471*9c5db199SXin Li bytes_to_skip = self._messages_size 472*9c5db199SXin Li in_messages = open("/var/log/messages") 473*9c5db199SXin Li in_messages.seek(bytes_to_skip) 474*9c5db199SXin Li out_messages = open(os.path.join(logdir, "messages"), "w") 475*9c5db199SXin Li out_messages.write(in_messages.read()) 476*9c5db199SXin Li in_messages.close() 477*9c5db199SXin Li out_messages.close() 478*9c5db199SXin Li except Exception as e: 479*9c5db199SXin Li logging.error("/var/log/messages collection failed with %s", e) 480*9c5db199SXin Li 481*9c5db199SXin Li 482*9c5db199SXin Li @staticmethod 483*9c5db199SXin Li def _read_sysinfo_keyvals(loggables, logdir): 484*9c5db199SXin Li keyval = {} 485*9c5db199SXin Li for log in loggables: 486*9c5db199SXin Li if log.log_in_keyval: 487*9c5db199SXin Li keyval["sysinfo-" + log.logf] = log.readline(logdir) 488*9c5db199SXin Li return keyval 489*9c5db199SXin Li 490*9c5db199SXin Li 491*9c5db199SXin Li def log_test_keyvals(self, test_sysinfodir): 492*9c5db199SXin Li """Generate keyval for the sysinfo. 493*9c5db199SXin Li 494*9c5db199SXin Li Collects keyval entries to be written in the test keyval. 495*9c5db199SXin Li 496*9c5db199SXin Li @param test_sysinfodir: The test's system info directory. 497*9c5db199SXin Li """ 498*9c5db199SXin Li keyval = {} 499*9c5db199SXin Li 500*9c5db199SXin Li # grab any loggables that should be in the keyval 501*9c5db199SXin Li keyval.update(self._read_sysinfo_keyvals(self.test_loggables, 502*9c5db199SXin Li test_sysinfodir)) 503*9c5db199SXin Li keyval.update(self._read_sysinfo_keyvals( 504*9c5db199SXin Li self.boot_loggables, os.path.join(test_sysinfodir, 505*9c5db199SXin Li 'reboot_current'))) 506*9c5db199SXin Li 507*9c5db199SXin Li # remove hostname from uname info 508*9c5db199SXin Li # Linux lpt36 2.6.18-smp-230.1 #1 [4069269] SMP Fri Oct 24 11:30:... 509*9c5db199SXin Li if "sysinfo-uname" in keyval: 510*9c5db199SXin Li kernel_vers = " ".join(keyval["sysinfo-uname"].split()[2:]) 511*9c5db199SXin Li keyval["sysinfo-uname"] = kernel_vers 512*9c5db199SXin Li 513*9c5db199SXin Li # grab the total avail memory, not used by sys tables 514*9c5db199SXin Li path = os.path.join(test_sysinfodir, "reboot_current", "meminfo") 515*9c5db199SXin Li if os.path.exists(path): 516*9c5db199SXin Li mem_data = open(path).read() 517*9c5db199SXin Li match = re.search(r'^MemTotal:\s+(\d+) kB$', mem_data, re.MULTILINE) 518*9c5db199SXin Li if match: 519*9c5db199SXin Li keyval["sysinfo-memtotal-in-kb"] = match.group(1) 520*9c5db199SXin Li 521*9c5db199SXin Li # guess the system's total physical memory, including sys tables 522*9c5db199SXin Li keyval["sysinfo-phys-mbytes"] = utils.rounded_memtotal()//1024 523*9c5db199SXin Li 524*9c5db199SXin Li # return what we collected 525*9c5db199SXin Li return keyval 526*9c5db199SXin Li 527*9c5db199SXin Lidef _run_loggables_ignoring_errors(loggables, output_dir): 528*9c5db199SXin Li """Runs the given loggables robustly. 529*9c5db199SXin Li 530*9c5db199SXin Li In the event of any one of the loggables raising an exception, we print a 531*9c5db199SXin Li traceback and continue on. 532*9c5db199SXin Li 533*9c5db199SXin Li @param loggables: An iterable of base_sysinfo.loggable objects. 534*9c5db199SXin Li @param output_dir: Path to the output directory. 535*9c5db199SXin Li """ 536*9c5db199SXin Li for log in loggables: 537*9c5db199SXin Li try: 538*9c5db199SXin Li log.run(output_dir) 539*9c5db199SXin Li except Exception: 540*9c5db199SXin Li logging.exception( 541*9c5db199SXin Li 'Failed to collect loggable %r to %s. Continuing...', 542*9c5db199SXin Li log, output_dir) 543*9c5db199SXin Li 544*9c5db199SXin Lidef get_journal_cursor(): 545*9c5db199SXin Li # TODO(yoshiki): remove journald related code: crbug.com/1066706 546*9c5db199SXin Li cmd = "/usr/bin/journalctl -n0 --show-cursor -q" 547*9c5db199SXin Li try: 548*9c5db199SXin Li cursor = utils.system_output(cmd) 549*9c5db199SXin Li prefix = "-- cursor: " 550*9c5db199SXin Li pos = cursor.find(prefix) + len(prefix) 551*9c5db199SXin Li return cursor[pos:] 552*9c5db199SXin Li except Exception as e: 553*9c5db199SXin Li logging.error("error running journalctl --show-cursor: %s", e) 554*9c5db199SXin Li 555*9c5db199SXin Lidef get_system_log_cursor(): 556*9c5db199SXin Li if not os.path.exists("/usr/sbin/croslog"): 557*9c5db199SXin Li return None 558*9c5db199SXin Li 559*9c5db199SXin Li cmd = "/usr/sbin/croslog --lines=0 --show-cursor --quiet" 560*9c5db199SXin Li try: 561*9c5db199SXin Li cursor = utils.system_output(cmd) 562*9c5db199SXin Li prefix = "-- cursor: " 563*9c5db199SXin Li pos = cursor.find(prefix) + len(prefix) 564*9c5db199SXin Li return cursor[pos:] 565*9c5db199SXin Li except Exception as e: 566*9c5db199SXin Li logging.error("error running croslog --show-cursor: %s", e) 567