1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright (c) 2011 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Liimport logging 7*9c5db199SXin Liimport os 8*9c5db199SXin Liimport stat 9*9c5db199SXin Li 10*9c5db199SXin Lifrom autotest_lib.client.common_lib import log 11*9c5db199SXin Lifrom autotest_lib.client.common_lib import error, utils, global_config 12*9c5db199SXin Lifrom autotest_lib.client.bin import base_sysinfo, utils 13*9c5db199SXin Lifrom autotest_lib.client.cros import constants 14*9c5db199SXin Lifrom autotest_lib.client.cros import tpm 15*9c5db199SXin Li 16*9c5db199SXin Li 17*9c5db199SXin Liget_value = global_config.global_config.get_config_value 18*9c5db199SXin Licollect_corefiles = get_value('CLIENT', 'collect_corefiles', 19*9c5db199SXin Li type=bool, default=False) 20*9c5db199SXin Li 21*9c5db199SXin Li 22*9c5db199SXin Lilogfile = base_sysinfo.logfile 23*9c5db199SXin Licommand = base_sysinfo.command 24*9c5db199SXin Li 25*9c5db199SXin Li 26*9c5db199SXin Liclass logdir(base_sysinfo.loggable): 27*9c5db199SXin Li """Represents a log directory.""" 28*9c5db199SXin Li 29*9c5db199SXin Li DEFAULT_EXCLUDES = ("**autoserv*", "**.journal",) 30*9c5db199SXin Li 31*9c5db199SXin Li def __init__(self, directory, excludes=DEFAULT_EXCLUDES): 32*9c5db199SXin Li super(logdir, self).__init__(directory, log_in_keyval=False) 33*9c5db199SXin Li self.dir = directory 34*9c5db199SXin Li self._excludes = excludes 35*9c5db199SXin Li self._infer_old_attributes() 36*9c5db199SXin Li 37*9c5db199SXin Li 38*9c5db199SXin Li def __setstate__(self, state): 39*9c5db199SXin Li """Unpickle handler 40*9c5db199SXin Li 41*9c5db199SXin Li When client tests are run without SSP, we pickle this object on the 42*9c5db199SXin Li server-side (using the version of the class deployed in the lab) and 43*9c5db199SXin Li unpickle it on the DUT (using the version of the class from the build). 44*9c5db199SXin Li This means that when adding a new attribute to this class, for a while 45*9c5db199SXin Li the server-side code does not populate that attribute. So, deal with 46*9c5db199SXin Li missing attributes in a valid way. 47*9c5db199SXin Li """ 48*9c5db199SXin Li self.__dict__ = state 49*9c5db199SXin Li if '_excludes' not in state: 50*9c5db199SXin Li self._excludes = self.DEFAULT_EXCLUDES 51*9c5db199SXin Li if self.additional_exclude: 52*9c5db199SXin Li self._excludes += tuple(self.additional_exclude) 53*9c5db199SXin Li 54*9c5db199SXin Li 55*9c5db199SXin Li def __repr__(self): 56*9c5db199SXin Li return "site_sysinfo.logdir(%r, %s)" % (self.dir, 57*9c5db199SXin Li self._excludes) 58*9c5db199SXin Li 59*9c5db199SXin Li 60*9c5db199SXin Li def __eq__(self, other): 61*9c5db199SXin Li if isinstance(other, logdir): 62*9c5db199SXin Li return (self.dir == other.dir and self._excludes == other._excludes) 63*9c5db199SXin Li elif isinstance(other, base_sysinfo.loggable): 64*9c5db199SXin Li return False 65*9c5db199SXin Li return NotImplemented 66*9c5db199SXin Li 67*9c5db199SXin Li 68*9c5db199SXin Li def __ne__(self, other): 69*9c5db199SXin Li result = self.__eq__(other) 70*9c5db199SXin Li if result is NotImplemented: 71*9c5db199SXin Li return result 72*9c5db199SXin Li return not result 73*9c5db199SXin Li 74*9c5db199SXin Li 75*9c5db199SXin Li def __hash__(self): 76*9c5db199SXin Li return hash(self.dir) + hash(self._excludes) 77*9c5db199SXin Li 78*9c5db199SXin Li 79*9c5db199SXin Li def run(self, log_dir): 80*9c5db199SXin Li """Copies this log directory to the specified directory. 81*9c5db199SXin Li 82*9c5db199SXin Li @param log_dir: The destination log directory. 83*9c5db199SXin Li """ 84*9c5db199SXin Li from_dir = os.path.realpath(self.dir) 85*9c5db199SXin Li if os.path.exists(from_dir): 86*9c5db199SXin Li parent_dir = os.path.dirname(from_dir) 87*9c5db199SXin Li utils.system("mkdir -p %s%s" % (log_dir, parent_dir)) 88*9c5db199SXin Li 89*9c5db199SXin Li excludes = [ 90*9c5db199SXin Li "--exclude=%s" % self._anchored_exclude_pattern(from_dir, x) 91*9c5db199SXin Li for x in self._excludes] 92*9c5db199SXin Li # Take source permissions and add ugo+r so files are accessible via 93*9c5db199SXin Li # archive server. 94*9c5db199SXin Li utils.system( 95*9c5db199SXin Li "rsync --no-perms --chmod=ugo+r -a --safe-links %s %s %s%s" 96*9c5db199SXin Li % (" ".join(excludes), from_dir, log_dir, parent_dir)) 97*9c5db199SXin Li 98*9c5db199SXin Li 99*9c5db199SXin Li def _anchored_exclude_pattern(self, from_dir, pattern): 100*9c5db199SXin Li return '/%s/%s' % (os.path.basename(from_dir), pattern) 101*9c5db199SXin Li 102*9c5db199SXin Li 103*9c5db199SXin Li def _infer_old_attributes(self): 104*9c5db199SXin Li """ Backwards compatibility attributes. 105*9c5db199SXin Li 106*9c5db199SXin Li YOU MUST NEVER DROP / REINTERPRET THESE. 107*9c5db199SXin Li A logdir object is pickled on server-side and unpickled on 108*9c5db199SXin Li client-side. This means that, when running aginst client-side code 109*9c5db199SXin Li from an older build, we need to be able to unpickle an instance of 110*9c5db199SXin Li logdir pickled from a newer version of the class. 111*9c5db199SXin Li 112*9c5db199SXin Li Some old attributes are not accurately handled via __setstate__, so we can't 113*9c5db199SXin Li drop them without breaking compatibility. 114*9c5db199SXin Li """ 115*9c5db199SXin Li additional_excludes = list(set(self._excludes) - 116*9c5db199SXin Li set(self.DEFAULT_EXCLUDES)) 117*9c5db199SXin Li if additional_excludes: 118*9c5db199SXin Li # Old API only allowed a single additional exclude. 119*9c5db199SXin Li # Best effort, keep the first one, throw the rest. 120*9c5db199SXin Li self.additional_exclude = additional_excludes[0] 121*9c5db199SXin Li else: 122*9c5db199SXin Li self.additional_exclude = None 123*9c5db199SXin Li 124*9c5db199SXin Li 125*9c5db199SXin Liclass file_stat(object): 126*9c5db199SXin Li """Store the file size and inode, used for retrieving new data in file.""" 127*9c5db199SXin Li def __init__(self, file_path): 128*9c5db199SXin Li """Collect the size and inode information of a file. 129*9c5db199SXin Li 130*9c5db199SXin Li @param file_path: full path to the file. 131*9c5db199SXin Li 132*9c5db199SXin Li """ 133*9c5db199SXin Li stat = os.stat(file_path) 134*9c5db199SXin Li # Start size of the file, skip that amount of bytes when do diff. 135*9c5db199SXin Li self.st_size = stat.st_size 136*9c5db199SXin Li # inode of the file. If inode is changed, treat this as a new file and 137*9c5db199SXin Li # copy the whole file. 138*9c5db199SXin Li self.st_ino = stat.st_ino 139*9c5db199SXin Li 140*9c5db199SXin Li 141*9c5db199SXin Liclass diffable_logdir(logdir): 142*9c5db199SXin Li """Represents a log directory that only new content will be copied. 143*9c5db199SXin Li 144*9c5db199SXin Li An instance of this class should be added in both 145*9c5db199SXin Li before_iteration_loggables and after_iteration_loggables. This is to 146*9c5db199SXin Li guarantee the file status information is collected when run method is 147*9c5db199SXin Li called in before_iteration_loggables, and diff is executed when run 148*9c5db199SXin Li method is called in after_iteration_loggables. 149*9c5db199SXin Li 150*9c5db199SXin Li """ 151*9c5db199SXin Li def __init__(self, directory, excludes=logdir.DEFAULT_EXCLUDES, 152*9c5db199SXin Li keep_file_hierarchy=True, append_diff_in_name=True): 153*9c5db199SXin Li """ 154*9c5db199SXin Li Constructor of a diffable_logdir instance. 155*9c5db199SXin Li 156*9c5db199SXin Li @param directory: directory to be diffed after an iteration finished. 157*9c5db199SXin Li @param excludes: path patterns to exclude for rsync. 158*9c5db199SXin Li @param keep_file_hierarchy: True if need to preserve full path, e.g., 159*9c5db199SXin Li sysinfo/var/log/sysstat, v.s. sysinfo/sysstat if it's False. 160*9c5db199SXin Li @param append_diff_in_name: True if you want to append '_diff' to the 161*9c5db199SXin Li folder name to indicate it's a diff, e.g., var/log_diff. Option 162*9c5db199SXin Li keep_file_hierarchy must be True for this to take effect. 163*9c5db199SXin Li 164*9c5db199SXin Li """ 165*9c5db199SXin Li super(diffable_logdir, self).__init__(directory, excludes) 166*9c5db199SXin Li self.keep_file_hierarchy = keep_file_hierarchy 167*9c5db199SXin Li self.append_diff_in_name = append_diff_in_name 168*9c5db199SXin Li # Init dictionary to store all file status for files in the directory. 169*9c5db199SXin Li self._log_stats = {} 170*9c5db199SXin Li 171*9c5db199SXin Li 172*9c5db199SXin Li def _get_init_status_of_src_dir(self, src_dir): 173*9c5db199SXin Li """Get initial status of files in src_dir folder. 174*9c5db199SXin Li 175*9c5db199SXin Li @param src_dir: directory to be diff-ed. 176*9c5db199SXin Li 177*9c5db199SXin Li """ 178*9c5db199SXin Li # Dictionary used to store the initial status of files in src_dir. 179*9c5db199SXin Li for file_path in self._get_all_files(src_dir): 180*9c5db199SXin Li self._log_stats[file_path] = file_stat(file_path) 181*9c5db199SXin Li self.file_stats_collected = True 182*9c5db199SXin Li 183*9c5db199SXin Li 184*9c5db199SXin Li def _get_all_files(self, path): 185*9c5db199SXin Li """Iterate through files in given path including subdirectories. 186*9c5db199SXin Li 187*9c5db199SXin Li @param path: root directory. 188*9c5db199SXin Li @return: an iterator that iterates through all files in given path 189*9c5db199SXin Li including subdirectories. 190*9c5db199SXin Li 191*9c5db199SXin Li """ 192*9c5db199SXin Li if not os.path.exists(path): 193*9c5db199SXin Li yield [] 194*9c5db199SXin Li for root, dirs, files in os.walk(path): 195*9c5db199SXin Li for f in files: 196*9c5db199SXin Li if f.startswith('autoserv'): 197*9c5db199SXin Li continue 198*9c5db199SXin Li if f.endswith('.journal') or f.endswith('.journal~'): 199*9c5db199SXin Li continue 200*9c5db199SXin Li full_path = os.path.join(root, f) 201*9c5db199SXin Li # Only list regular files or symlinks to those (os.stat follows 202*9c5db199SXin Li # symlinks) 203*9c5db199SXin Li try: 204*9c5db199SXin Li if stat.S_ISREG(os.stat(full_path).st_mode): 205*9c5db199SXin Li yield full_path 206*9c5db199SXin Li except OSError: 207*9c5db199SXin Li # Semi-often a source of a symlink will get deleted, which 208*9c5db199SXin Li # causes a crash when `stat`d, thus breaks the the hook. 209*9c5db199SXin Li # Instead of quietly crashing, we will just not collect 210*9c5db199SXin Li # the missing of file. 211*9c5db199SXin Li logging.debug( 212*9c5db199SXin Li 'File {} could not stat & will not be collected'. 213*9c5db199SXin Li format(full_path)) 214*9c5db199SXin Li continue 215*9c5db199SXin Li 216*9c5db199SXin Li 217*9c5db199SXin Li def _copy_new_data_in_file(self, file_path, src_dir, dest_dir): 218*9c5db199SXin Li """Copy all new data in a file to target directory. 219*9c5db199SXin Li 220*9c5db199SXin Li @param file_path: full path to the file to be copied. 221*9c5db199SXin Li @param src_dir: source directory to do the diff. 222*9c5db199SXin Li @param dest_dir: target directory to store new data of src_dir. 223*9c5db199SXin Li 224*9c5db199SXin Li """ 225*9c5db199SXin Li bytes_to_skip = 0 226*9c5db199SXin Li if file_path in self._log_stats: 227*9c5db199SXin Li prev_stat = self._log_stats[file_path] 228*9c5db199SXin Li new_stat = os.stat(file_path) 229*9c5db199SXin Li if new_stat.st_ino == prev_stat.st_ino: 230*9c5db199SXin Li bytes_to_skip = prev_stat.st_size 231*9c5db199SXin Li if new_stat.st_size == bytes_to_skip: 232*9c5db199SXin Li return 233*9c5db199SXin Li elif new_stat.st_size < prev_stat.st_size: 234*9c5db199SXin Li # File is modified to a smaller size, copy whole file. 235*9c5db199SXin Li bytes_to_skip = 0 236*9c5db199SXin Li try: 237*9c5db199SXin Li with open(file_path, 'rb') as in_log: 238*9c5db199SXin Li if bytes_to_skip > 0: 239*9c5db199SXin Li in_log.seek(bytes_to_skip) 240*9c5db199SXin Li # Skip src_dir in path, e.g., src_dir/[sub_dir]/file_name. 241*9c5db199SXin Li target_path = os.path.join(dest_dir, 242*9c5db199SXin Li os.path.relpath(file_path, src_dir)) 243*9c5db199SXin Li target_dir = os.path.dirname(target_path) 244*9c5db199SXin Li if not os.path.exists(target_dir): 245*9c5db199SXin Li os.makedirs(target_dir) 246*9c5db199SXin Li with open(target_path, 'wb') as out_log: 247*9c5db199SXin Li out_log.write(in_log.read()) 248*9c5db199SXin Li except IOError as e: 249*9c5db199SXin Li logging.error('Diff %s failed with error: %s', file_path, e) 250*9c5db199SXin Li 251*9c5db199SXin Li 252*9c5db199SXin Li def _log_diff(self, src_dir, dest_dir): 253*9c5db199SXin Li """Log all of the new data in src_dir to dest_dir. 254*9c5db199SXin Li 255*9c5db199SXin Li @param src_dir: source directory to do the diff. 256*9c5db199SXin Li @param dest_dir: target directory to store new data of src_dir. 257*9c5db199SXin Li 258*9c5db199SXin Li """ 259*9c5db199SXin Li if self.keep_file_hierarchy: 260*9c5db199SXin Li dir = src_dir.lstrip('/') 261*9c5db199SXin Li if self.append_diff_in_name: 262*9c5db199SXin Li dir = dir.rstrip('/') + '_diff' 263*9c5db199SXin Li dest_dir = os.path.join(dest_dir, dir) 264*9c5db199SXin Li 265*9c5db199SXin Li if not os.path.exists(dest_dir): 266*9c5db199SXin Li os.makedirs(dest_dir) 267*9c5db199SXin Li 268*9c5db199SXin Li for src_file in self._get_all_files(src_dir): 269*9c5db199SXin Li self._copy_new_data_in_file(src_file, src_dir, dest_dir) 270*9c5db199SXin Li 271*9c5db199SXin Li def run(self, log_dir, collect_init_status=True, collect_all=False): 272*9c5db199SXin Li """Copies new content from self.dir to the destination log_dir. 273*9c5db199SXin Li 274*9c5db199SXin Li @param log_dir: The destination log directory. 275*9c5db199SXin Li @param collect_init_status: Set to True if run method is called to 276*9c5db199SXin Li collect the initial status of files. 277*9c5db199SXin Li @param collect_all: Set to True to force to collect all files. 278*9c5db199SXin Li 279*9c5db199SXin Li """ 280*9c5db199SXin Li if collect_init_status: 281*9c5db199SXin Li self._get_init_status_of_src_dir(self.dir) 282*9c5db199SXin Li elif os.path.exists(self.dir): 283*9c5db199SXin Li # Always create a copy of the new logs to help debugging. 284*9c5db199SXin Li self._log_diff(self.dir, log_dir) 285*9c5db199SXin Li if collect_all: 286*9c5db199SXin Li logdir_temp = logdir(self.dir) 287*9c5db199SXin Li logdir_temp.run(log_dir) 288*9c5db199SXin Li 289*9c5db199SXin Li 290*9c5db199SXin Liclass purgeable_logdir(logdir): 291*9c5db199SXin Li """Represents a log directory that will be purged.""" 292*9c5db199SXin Li def __init__(self, directory, excludes=logdir.DEFAULT_EXCLUDES): 293*9c5db199SXin Li super(purgeable_logdir, self).__init__(directory, excludes) 294*9c5db199SXin Li 295*9c5db199SXin Li def run(self, log_dir): 296*9c5db199SXin Li """Copies this log dir to the destination dir, then purges the source. 297*9c5db199SXin Li 298*9c5db199SXin Li @param log_dir: The destination log directory. 299*9c5db199SXin Li """ 300*9c5db199SXin Li super(purgeable_logdir, self).run(log_dir) 301*9c5db199SXin Li 302*9c5db199SXin Li if os.path.exists(self.dir): 303*9c5db199SXin Li utils.system("rm -rf %s/*" % (self.dir)) 304*9c5db199SXin Li 305*9c5db199SXin Li 306*9c5db199SXin Liclass purged_on_init_logdir(logdir): 307*9c5db199SXin Li """Represents a log directory that is purged *when initialized*.""" 308*9c5db199SXin Li 309*9c5db199SXin Li def __init__(self, directory, excludes=logdir.DEFAULT_EXCLUDES): 310*9c5db199SXin Li super(purged_on_init_logdir, self).__init__(directory, excludes) 311*9c5db199SXin Li 312*9c5db199SXin Li if os.path.exists(self.dir): 313*9c5db199SXin Li utils.system("rm -rf %s/*" % (self.dir)) 314*9c5db199SXin Li 315*9c5db199SXin Li 316*9c5db199SXin Liclass site_sysinfo(base_sysinfo.base_sysinfo): 317*9c5db199SXin Li """Represents site system info.""" 318*9c5db199SXin Li def __init__(self, job_resultsdir, version=None): 319*9c5db199SXin Li super(site_sysinfo, self).__init__(job_resultsdir) 320*9c5db199SXin Li crash_exclude_string = None 321*9c5db199SXin Li if not collect_corefiles: 322*9c5db199SXin Li crash_exclude_string = "*.core" 323*9c5db199SXin Li 324*9c5db199SXin Li # This is added in before and after_iteration_loggables. When run is 325*9c5db199SXin Li # called in before_iteration_loggables, it collects file status in 326*9c5db199SXin Li # the directory. When run is called in after_iteration_loggables, diff 327*9c5db199SXin Li # is executed. 328*9c5db199SXin Li # self.diffable_loggables is only initialized if the instance does not 329*9c5db199SXin Li # have this attribute yet. The sysinfo instance could be loaded 330*9c5db199SXin Li # from an earlier pickle dump, which has already initialized attribute 331*9c5db199SXin Li # self.diffable_loggables. 332*9c5db199SXin Li if not hasattr(self, 'diffable_loggables'): 333*9c5db199SXin Li diffable_log = diffable_logdir(constants.LOG_DIR) 334*9c5db199SXin Li self.diffable_loggables = set() 335*9c5db199SXin Li self.diffable_loggables.add(diffable_log) 336*9c5db199SXin Li 337*9c5db199SXin Li # add in some extra command logging 338*9c5db199SXin Li self.boot_loggables.add(command("ls -l /boot", 339*9c5db199SXin Li "boot_file_list")) 340*9c5db199SXin Li self.before_iteration_loggables.add( 341*9c5db199SXin Li command(constants.CHROME_VERSION_COMMAND, "chrome_version")) 342*9c5db199SXin Li self.boot_loggables.add(command("crossystem", "crossystem")) 343*9c5db199SXin Li self.test_loggables.add( 344*9c5db199SXin Li purgeable_logdir( 345*9c5db199SXin Li os.path.join(constants.CRYPTOHOME_MOUNT_PT, "log"))) 346*9c5db199SXin Li 347*9c5db199SXin Li # We do *not* want to purge crashes after iteration to allow post-test 348*9c5db199SXin Li # infrastructure to collect them as well. Instead, purge them before. 349*9c5db199SXin Li # TODO(mutexlox, ayatane): test_runner should handle the purging. 350*9c5db199SXin Li self.after_iteration_loggables.add( 351*9c5db199SXin Li purged_on_init_logdir(os.path.join( 352*9c5db199SXin Li constants.CRYPTOHOME_MOUNT_PT, "crash"), 353*9c5db199SXin Li excludes=logdir.DEFAULT_EXCLUDES + 354*9c5db199SXin Li (crash_exclude_string, ))) 355*9c5db199SXin Li 356*9c5db199SXin Li self.test_loggables.add( 357*9c5db199SXin Li purgeable_logdir(constants.CRASH_DIR, 358*9c5db199SXin Li excludes=logdir.DEFAULT_EXCLUDES + 359*9c5db199SXin Li (crash_exclude_string, ))) 360*9c5db199SXin Li 361*9c5db199SXin Li self.test_loggables.add( 362*9c5db199SXin Li logfile(os.path.join(constants.USER_DATA_DIR, 363*9c5db199SXin Li ".Google/Google Talk Plugin/gtbplugin.log"))) 364*9c5db199SXin Li 365*9c5db199SXin Li # purged_on_init_logdir not compatible with client R86 and prior. 366*9c5db199SXin Li if version and int(version) > 86: 367*9c5db199SXin Li self.test_loggables.add( 368*9c5db199SXin Li purged_on_init_logdir(constants.CRASH_DIR, 369*9c5db199SXin Li excludes=logdir.DEFAULT_EXCLUDES + 370*9c5db199SXin Li (crash_exclude_string, ))) 371*9c5db199SXin Li # Collect files under /tmp/crash_reporter, which contain the procfs 372*9c5db199SXin Li # copy of those crashed processes whose core file didn't get converted 373*9c5db199SXin Li # into minidump. We need these additional files for retrospective analysis 374*9c5db199SXin Li # of the conversion failure. 375*9c5db199SXin Li self.test_loggables.add( 376*9c5db199SXin Li purgeable_logdir(constants.CRASH_REPORTER_RESIDUE_DIR)) 377*9c5db199SXin Li 378*9c5db199SXin Li 379*9c5db199SXin Li @log.log_and_ignore_errors("pre-test sysinfo error:") 380*9c5db199SXin Li def log_before_each_test(self, test): 381*9c5db199SXin Li """Logging hook called before a test starts. 382*9c5db199SXin Li 383*9c5db199SXin Li @param test: A test object. 384*9c5db199SXin Li """ 385*9c5db199SXin Li super(site_sysinfo, self).log_before_each_test(test) 386*9c5db199SXin Li 387*9c5db199SXin Li try: 388*9c5db199SXin Li for log in self.diffable_loggables: 389*9c5db199SXin Li log.run(log_dir=None, collect_init_status=True) 390*9c5db199SXin Li except Exception as e: 391*9c5db199SXin Li logging.warning("Exception hit during log_before_each_test %s", e) 392*9c5db199SXin Li 393*9c5db199SXin Li @log.log_and_ignore_errors("post-test sysinfo error:") 394*9c5db199SXin Li def log_after_each_test(self, test): 395*9c5db199SXin Li """Logging hook called after a test finishs. 396*9c5db199SXin Li 397*9c5db199SXin Li @param test: A test object. 398*9c5db199SXin Li """ 399*9c5db199SXin Li super(site_sysinfo, self).log_after_each_test(test) 400*9c5db199SXin Li 401*9c5db199SXin Li test_sysinfodir = self._get_sysinfodir(test.outputdir) 402*9c5db199SXin Li 403*9c5db199SXin Li for log in self.diffable_loggables: 404*9c5db199SXin Li log.run(log_dir=test_sysinfodir, 405*9c5db199SXin Li collect_init_status=False, 406*9c5db199SXin Li collect_all=not test.success or test.collect_full_logs) 407*9c5db199SXin Li 408*9c5db199SXin Li 409*9c5db199SXin Li def _get_chrome_version(self): 410*9c5db199SXin Li """Gets the Chrome version number and milestone as strings. 411*9c5db199SXin Li 412*9c5db199SXin Li Invokes "chrome --version" to get the version number and milestone. 413*9c5db199SXin Li 414*9c5db199SXin Li @return A tuple (chrome_ver, milestone) where "chrome_ver" is the 415*9c5db199SXin Li current Chrome version number as a string (in the form "W.X.Y.Z") 416*9c5db199SXin Li and "milestone" is the first component of the version number 417*9c5db199SXin Li (the "W" from "W.X.Y.Z"). If the version number cannot be parsed 418*9c5db199SXin Li in the "W.X.Y.Z" format, the "chrome_ver" will be the full output 419*9c5db199SXin Li of "chrome --version" and the milestone will be the empty string. 420*9c5db199SXin Li 421*9c5db199SXin Li """ 422*9c5db199SXin Li version_string = utils.system_output(constants.CHROME_VERSION_COMMAND, 423*9c5db199SXin Li ignore_status=True) 424*9c5db199SXin Li return utils.parse_chrome_version(version_string) 425*9c5db199SXin Li 426*9c5db199SXin Li 427*9c5db199SXin Li def log_test_keyvals(self, test_sysinfodir): 428*9c5db199SXin Li """Generate keyval for the sysinfo. 429*9c5db199SXin Li 430*9c5db199SXin Li Collects keyval entries to be written in the test keyval. 431*9c5db199SXin Li 432*9c5db199SXin Li @param test_sysinfodir: The test's system info directory. 433*9c5db199SXin Li """ 434*9c5db199SXin Li keyval = super(site_sysinfo, self).log_test_keyvals(test_sysinfodir) 435*9c5db199SXin Li 436*9c5db199SXin Li lsb_lines = utils.system_output( 437*9c5db199SXin Li "cat /etc/lsb-release", 438*9c5db199SXin Li ignore_status=True).splitlines() 439*9c5db199SXin Li lsb_dict = dict(item.split("=") for item in lsb_lines) 440*9c5db199SXin Li 441*9c5db199SXin Li for lsb_key in lsb_dict.keys(): 442*9c5db199SXin Li # Special handling for build number 443*9c5db199SXin Li if lsb_key == "CHROMEOS_RELEASE_DESCRIPTION": 444*9c5db199SXin Li keyval["CHROMEOS_BUILD"] = ( 445*9c5db199SXin Li lsb_dict[lsb_key].rstrip(")").split(" ")[3]) 446*9c5db199SXin Li keyval[lsb_key] = lsb_dict[lsb_key] 447*9c5db199SXin Li 448*9c5db199SXin Li # Get the hwid (hardware ID), if applicable. 449*9c5db199SXin Li try: 450*9c5db199SXin Li keyval["hwid"] = utils.system_output('crossystem hwid') 451*9c5db199SXin Li except error.CmdError: 452*9c5db199SXin Li # The hwid may not be available (e.g, when running on a VM). 453*9c5db199SXin Li # If the output of 'crossystem mainfw_type' is 'nonchrome', then 454*9c5db199SXin Li # we expect the hwid to not be avilable, and we can proceed in this 455*9c5db199SXin Li # case. Otherwise, the hwid is missing unexpectedly. 456*9c5db199SXin Li mainfw_type = utils.system_output('crossystem mainfw_type') 457*9c5db199SXin Li if mainfw_type == 'nonchrome': 458*9c5db199SXin Li logging.info( 459*9c5db199SXin Li 'HWID not available; not logging it as a test keyval.') 460*9c5db199SXin Li else: 461*9c5db199SXin Li logging.exception('HWID expected but could not be identified; ' 462*9c5db199SXin Li 'output of "crossystem mainfw_type" is "%s"', 463*9c5db199SXin Li mainfw_type) 464*9c5db199SXin Li raise 465*9c5db199SXin Li 466*9c5db199SXin Li # Get the chrome version and milestone numbers. 467*9c5db199SXin Li keyval["CHROME_VERSION"], keyval["MILESTONE"] = ( 468*9c5db199SXin Li self._get_chrome_version()) 469*9c5db199SXin Li 470*9c5db199SXin Li # Get the dictionary attack counter. 471*9c5db199SXin Li keyval["TPM_DICTIONARY_ATTACK_COUNTER"] = ( 472*9c5db199SXin Li tpm.get_tpm_da_info().get( 473*9c5db199SXin Li 'dictionary_attack_counter', 474*9c5db199SXin Li 'Failed to query tpm_manager')) 475*9c5db199SXin Li 476*9c5db199SXin Li # Return the updated keyvals. 477*9c5db199SXin Li return keyval 478*9c5db199SXin Li 479*9c5db199SXin Li 480*9c5db199SXin Li def add_logdir(self, loggable): 481*9c5db199SXin Li """Collect files in log_path to sysinfo folder. 482*9c5db199SXin Li 483*9c5db199SXin Li This method can be called from a control file for test to collect files 484*9c5db199SXin Li in a specified folder. autotest creates a folder [test result 485*9c5db199SXin Li dir]/sysinfo folder with the full path of log_path and copy all files in 486*9c5db199SXin Li log_path to that folder. 487*9c5db199SXin Li 488*9c5db199SXin Li @param loggable: A logdir instance corresponding to the logs to collect. 489*9c5db199SXin Li """ 490*9c5db199SXin Li self.test_loggables.add(loggable) 491