xref: /aosp_15_r20/external/autotest/client/bin/base_sysinfo.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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