Blame cloudinit/analyze/show.py

Packit Service a04d08
#   Copyright (C) 2016 Canonical Ltd.
Packit Service a04d08
#
Packit Service a04d08
#   Author: Ryan Harper <ryan.harper@canonical.com>
Packit Service a04d08
#
Packit Service a04d08
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service a04d08
Packit Service a04d08
import base64
Packit Service a04d08
import datetime
Packit Service a04d08
import json
Packit Service a04d08
import os
Packit Service a04d08
import time
Packit Service a04d08
import sys
Packit Service a04d08
Packit Service 9bfd13
from cloudinit import subp
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
from cloudinit.distros import uses_systemd
Packit Service a04d08
Packit Service 9bfd13
# Example events:
Packit Service 9bfd13
#     {
Packit Service 9bfd13
#             "description": "executing late commands",
Packit Service 9bfd13
#             "event_type": "start",
Packit Service 9bfd13
#             "level": "INFO",
Packit Service 9bfd13
#             "name": "cmd-install/stage-late"
Packit Service 9bfd13
#             "origin": "cloudinit",
Packit Service 9bfd13
#             "timestamp": 1461164249.1590767,
Packit Service 9bfd13
#     }
Packit Service 9bfd13
#     {
Packit Service 9bfd13
#         "description": "executing late commands",
Packit Service 9bfd13
#         "event_type": "finish",
Packit Service 9bfd13
#         "level": "INFO",
Packit Service 9bfd13
#         "name": "cmd-install/stage-late",
Packit Service 9bfd13
#         "origin": "cloudinit",
Packit Service 9bfd13
#         "result": "SUCCESS",
Packit Service 9bfd13
#         "timestamp": 1461164249.1590767
Packit Service 9bfd13
#     }
Packit Service 9bfd13
Packit Service a04d08
format_key = {
Packit Service a04d08
    '%d': 'delta',
Packit Service a04d08
    '%D': 'description',
Packit Service a04d08
    '%E': 'elapsed',
Packit Service a04d08
    '%e': 'event_type',
Packit Service a04d08
    '%I': 'indent',
Packit Service a04d08
    '%l': 'level',
Packit Service a04d08
    '%n': 'name',
Packit Service a04d08
    '%o': 'origin',
Packit Service a04d08
    '%r': 'result',
Packit Service a04d08
    '%t': 'timestamp',
Packit Service a04d08
    '%T': 'total_time',
Packit Service a04d08
}
Packit Service a04d08
Packit Service a04d08
formatting_help = " ".join(["{0}: {1}".format(k.replace('%', '%%'), v)
Packit Service a04d08
                           for k, v in format_key.items()])
Packit Service a04d08
SUCCESS_CODE = 'successful'
Packit Service a04d08
FAIL_CODE = 'failure'
Packit Service a04d08
CONTAINER_CODE = 'container'
Packit Service a04d08
TIMESTAMP_UNKNOWN = (FAIL_CODE, -1, -1, -1)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def format_record(msg, event):
Packit Service a04d08
    for i, j in format_key.items():
Packit Service a04d08
        if i in msg:
Packit Service a04d08
            # ensure consistent formatting of time values
Packit Service a04d08
            if j in ['delta', 'elapsed', 'timestamp']:
Packit Service a04d08
                msg = msg.replace(i, "{%s:08.5f}" % j)
Packit Service a04d08
            else:
Packit Service a04d08
                msg = msg.replace(i, "{%s}" % j)
Packit Service a04d08
    return msg.format(**event)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def dump_event_files(event):
Packit Service a04d08
    content = dict((k, v) for k, v in event.items() if k not in ['content'])
Packit Service a04d08
    files = content['files']
Packit Service a04d08
    saved = []
Packit Service a04d08
    for f in files:
Packit Service a04d08
        fname = f['path']
Packit Service a04d08
        fn_local = os.path.basename(fname)
Packit Service a04d08
        fcontent = base64.b64decode(f['content']).decode('ascii')
Packit Service a04d08
        util.write_file(fn_local, fcontent)
Packit Service a04d08
        saved.append(fn_local)
Packit Service a04d08
Packit Service a04d08
    return saved
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def event_name(event):
Packit Service a04d08
    if event:
Packit Service a04d08
        return event.get('name')
Packit Service a04d08
    return None
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def event_type(event):
Packit Service a04d08
    if event:
Packit Service a04d08
        return event.get('event_type')
Packit Service a04d08
    return None
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def event_parent(event):
Packit Service a04d08
    if event:
Packit Service a04d08
        return event_name(event).split("/")[0]
Packit Service a04d08
    return None
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def event_timestamp(event):
Packit Service a04d08
    return float(event.get('timestamp'))
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def event_datetime(event):
Packit Service a04d08
    return datetime.datetime.utcfromtimestamp(event_timestamp(event))
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def delta_seconds(t1, t2):
Packit Service a04d08
    return (t2 - t1).total_seconds()
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def event_duration(start, finish):
Packit Service a04d08
    return delta_seconds(event_datetime(start), event_datetime(finish))
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def event_record(start_time, start, finish):
Packit Service a04d08
    record = finish.copy()
Packit Service a04d08
    record.update({
Packit Service a04d08
        'delta': event_duration(start, finish),
Packit Service a04d08
        'elapsed': delta_seconds(start_time, event_datetime(start)),
Packit Service a04d08
        'indent': '|' + ' ' * (event_name(start).count('/') - 1) + '`->',
Packit Service a04d08
    })
Packit Service a04d08
Packit Service a04d08
    return record
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def total_time_record(total_time):
Packit Service a04d08
    return 'Total Time: %3.5f seconds\n' % total_time
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
class SystemctlReader(object):
Packit Service a04d08
    '''
Packit Service a04d08
    Class for dealing with all systemctl subp calls in a consistent manner.
Packit Service a04d08
    '''
Packit Service a04d08
    def __init__(self, property, parameter=None):
Packit Service a04d08
        self.epoch = None
Packit Service a04d08
        self.args = ['/bin/systemctl', 'show']
Packit Service a04d08
        if parameter:
Packit Service a04d08
            self.args.append(parameter)
Packit Service a04d08
        self.args.extend(['-p', property])
Packit Service a04d08
        # Don't want the init of our object to break. Instead of throwing
Packit Service a04d08
        # an exception, set an error code that gets checked when data is
Packit Service a04d08
        # requested from the object
Packit Service a04d08
        self.failure = self.subp()
Packit Service a04d08
Packit Service a04d08
    def subp(self):
Packit Service a04d08
        '''
Packit Service a04d08
        Make a subp call based on set args and handle errors by setting
Packit Service a04d08
        failure code
Packit Service a04d08
Packit Service a04d08
        :return: whether the subp call failed or not
Packit Service a04d08
        '''
Packit Service a04d08
        try:
Packit Service 9bfd13
            value, err = subp.subp(self.args, capture=True)
Packit Service a04d08
            if err:
Packit Service a04d08
                return err
Packit Service a04d08
            self.epoch = value
Packit Service a04d08
            return None
Packit Service a04d08
        except Exception as systemctl_fail:
Packit Service a04d08
            return systemctl_fail
Packit Service a04d08
Packit Service a04d08
    def parse_epoch_as_float(self):
Packit Service a04d08
        '''
Packit Service a04d08
        If subp call succeeded, return the timestamp from subp as a float.
Packit Service a04d08
Packit Service a04d08
        :return: timestamp as a float
Packit Service a04d08
        '''
Packit Service a04d08
        # subp has 2 ways to fail: it either fails and throws an exception,
Packit Service a04d08
        # or returns an error code. Raise an exception here in order to make
Packit Service a04d08
        # sure both scenarios throw exceptions
Packit Service a04d08
        if self.failure:
Packit Service a04d08
            raise RuntimeError('Subprocess call to systemctl has failed, '
Packit Service a04d08
                               'returning error code ({})'
Packit Service a04d08
                               .format(self.failure))
Packit Service a04d08
        # Output from systemctl show has the format Property=Value.
Packit Service a04d08
        # For example, UserspaceMonotonic=1929304
Packit Service a04d08
        timestamp = self.epoch.split('=')[1]
Packit Service a04d08
        # Timestamps reported by systemctl are in microseconds, converting
Packit Service a04d08
        return float(timestamp) / 1000000
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def dist_check_timestamp():
Packit Service a04d08
    '''
Packit Service a04d08
    Determine which init system a particular linux distro is using.
Packit Service a04d08
    Each init system (systemd, upstart, etc) has a different way of
Packit Service a04d08
    providing timestamps.
Packit Service a04d08
Packit Service a04d08
    :return: timestamps of kernelboot, kernelendboot, and cloud-initstart
Packit Service a04d08
    or TIMESTAMP_UNKNOWN if the timestamps cannot be retrieved.
Packit Service a04d08
    '''
Packit Service a04d08
Packit Service a04d08
    if uses_systemd():
Packit Service a04d08
        return gather_timestamps_using_systemd()
Packit Service a04d08
Packit Service a04d08
    # Use dmesg to get timestamps if the distro does not have systemd
Packit Service a04d08
    if util.is_FreeBSD() or 'gentoo' in \
Packit Service a04d08
            util.system_info()['system'].lower():
Packit Service a04d08
        return gather_timestamps_using_dmesg()
Packit Service a04d08
Packit Service a04d08
    # this distro doesn't fit anything that is supported by cloud-init. just
Packit Service a04d08
    # return error codes
Packit Service a04d08
    return TIMESTAMP_UNKNOWN
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def gather_timestamps_using_dmesg():
Packit Service a04d08
    '''
Packit Service a04d08
    Gather timestamps that corresponds to kernel begin initialization,
Packit Service a04d08
    kernel finish initialization using dmesg as opposed to systemctl
Packit Service a04d08
Packit Service a04d08
    :return: the two timestamps plus a dummy timestamp to keep consistency
Packit Service a04d08
    with gather_timestamps_using_systemd
Packit Service a04d08
    '''
Packit Service a04d08
    try:
Packit Service 9bfd13
        data, _ = subp.subp(['dmesg'], capture=True)
Packit Service a04d08
        split_entries = data[0].splitlines()
Packit Service a04d08
        for i in split_entries:
Packit Service a04d08
            if i.decode('UTF-8').find('user') != -1:
Packit Service a04d08
                splitup = i.decode('UTF-8').split()
Packit Service a04d08
                stripped = splitup[1].strip(']')
Packit Service a04d08
Packit Service a04d08
                # kernel timestamp from dmesg is equal to 0,
Packit Service a04d08
                # with the userspace timestamp relative to it.
Packit Service a04d08
                user_space_timestamp = float(stripped)
Packit Service a04d08
                kernel_start = float(time.time()) - float(util.uptime())
Packit Service a04d08
                kernel_end = kernel_start + user_space_timestamp
Packit Service a04d08
Packit Service a04d08
                # systemd wont start cloud-init in this case,
Packit Service a04d08
                # so we cannot get that timestamp
Packit Service a04d08
                return SUCCESS_CODE, kernel_start, kernel_end, \
Packit Service a04d08
                    kernel_end
Packit Service a04d08
Packit Service a04d08
    except Exception:
Packit Service a04d08
        pass
Packit Service a04d08
    return TIMESTAMP_UNKNOWN
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def gather_timestamps_using_systemd():
Packit Service a04d08
    '''
Packit Service a04d08
    Gather timestamps that corresponds to kernel begin initialization,
Packit Service a04d08
    kernel finish initialization. and cloud-init systemd unit activation
Packit Service a04d08
Packit Service a04d08
    :return: the three timestamps
Packit Service a04d08
    '''
Packit Service a04d08
    kernel_start = float(time.time()) - float(util.uptime())
Packit Service a04d08
    try:
Packit Service a04d08
        delta_k_end = SystemctlReader('UserspaceTimestampMonotonic')\
Packit Service a04d08
            .parse_epoch_as_float()
Packit Service a04d08
        delta_ci_s = SystemctlReader('InactiveExitTimestampMonotonic',
Packit Service a04d08
                                     'cloud-init-local').parse_epoch_as_float()
Packit Service a04d08
        base_time = kernel_start
Packit Service a04d08
        status = SUCCESS_CODE
Packit Service a04d08
        # lxc based containers do not set their monotonic zero point to be when
Packit Service a04d08
        # the container starts, instead keep using host boot as zero point
Packit Service a04d08
        # time.CLOCK_MONOTONIC_RAW is only available in python 3.3
Packit Service a04d08
        if util.is_container():
Packit Service a04d08
            # clock.monotonic also uses host boot as zero point
Packit Service a04d08
            if sys.version_info >= (3, 3):
Packit Service a04d08
                base_time = float(time.time()) - float(time.monotonic())
Packit Service a04d08
                # TODO: lxcfs automatically truncates /proc/uptime to seconds
Packit Service a04d08
                # in containers when https://github.com/lxc/lxcfs/issues/292
Packit Service a04d08
                # is fixed, util.uptime() should be used instead of stat on
Packit Service a04d08
                try:
Packit Service a04d08
                    file_stat = os.stat('/proc/1/cmdline')
Packit Service a04d08
                    kernel_start = file_stat.st_atime
Packit Service a04d08
                except OSError as err:
Packit Service a04d08
                    raise RuntimeError('Could not determine container boot '
Packit Service a04d08
                                       'time from /proc/1/cmdline. ({})'
Packit Service 9bfd13
                                       .format(err)) from err
Packit Service a04d08
                status = CONTAINER_CODE
Packit Service a04d08
            else:
Packit Service a04d08
                status = FAIL_CODE
Packit Service a04d08
        kernel_end = base_time + delta_k_end
Packit Service a04d08
        cloudinit_sysd = base_time + delta_ci_s
Packit Service a04d08
Packit Service a04d08
    except Exception as e:
Packit Service a04d08
        # Except ALL exceptions as Systemctl reader can throw many different
Packit Service a04d08
        # errors, but any failure in systemctl means that timestamps cannot be
Packit Service a04d08
        # obtained
Packit Service a04d08
        print(e)
Packit Service a04d08
        return TIMESTAMP_UNKNOWN
Packit Service a04d08
    return status, kernel_start, kernel_end, cloudinit_sysd
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def generate_records(events, blame_sort=False,
Packit Service a04d08
                     print_format="(%n) %d seconds in %I%D",
Packit Service a04d08
                     dump_files=False, log_datafiles=False):
Packit Service a04d08
    '''
Packit Service a04d08
    Take in raw events and create parent-child dependencies between events
Packit Service a04d08
    in order to order events in chronological order.
Packit Service a04d08
Packit Service a04d08
    :param events: JSONs from dump that represents events taken from logs
Packit Service a04d08
    :param blame_sort: whether to sort by timestamp or by time taken.
Packit Service a04d08
    :param print_format: formatting to represent event, time stamp,
Packit Service a04d08
    and time taken by the event in one line
Packit Service a04d08
    :param dump_files: whether to dump files into JSONs
Packit Service a04d08
    :param log_datafiles: whether or not to log events generated
Packit Service a04d08
Packit Service a04d08
    :return: boot records ordered chronologically
Packit Service a04d08
    '''
Packit Service a04d08
Packit Service a04d08
    sorted_events = sorted(events, key=lambda x: x['timestamp'])
Packit Service a04d08
    records = []
Packit Service a04d08
    start_time = None
Packit Service a04d08
    total_time = 0.0
Packit Service a04d08
    stage_start_time = {}
Packit Service a04d08
    stages_seen = []
Packit Service a04d08
    boot_records = []
Packit Service a04d08
Packit Service a04d08
    unprocessed = []
Packit Service a04d08
    for e in range(0, len(sorted_events)):
Packit Service a04d08
        event = events[e]
Packit Service a04d08
        try:
Packit Service a04d08
            next_evt = events[e + 1]
Packit Service a04d08
        except IndexError:
Packit Service a04d08
            next_evt = None
Packit Service a04d08
Packit Service a04d08
        if event_type(event) == 'start':
Packit Service a04d08
            if event.get('name') in stages_seen:
Packit Service a04d08
                records.append(total_time_record(total_time))
Packit Service a04d08
                boot_records.append(records)
Packit Service a04d08
                records = []
Packit Service a04d08
                start_time = None
Packit Service a04d08
                total_time = 0.0
Packit Service a04d08
Packit Service a04d08
            if start_time is None:
Packit Service a04d08
                stages_seen = []
Packit Service a04d08
                start_time = event_datetime(event)
Packit Service a04d08
                stage_start_time[event_parent(event)] = start_time
Packit Service a04d08
Packit Service a04d08
            # see if we have a pair
Packit Service a04d08
            if event_name(event) == event_name(next_evt):
Packit Service a04d08
                if event_type(next_evt) == 'finish':
Packit Service a04d08
                    records.append(format_record(print_format,
Packit Service a04d08
                                                 event_record(start_time,
Packit Service a04d08
                                                              event,
Packit Service a04d08
                                                              next_evt)))
Packit Service a04d08
            else:
Packit Service a04d08
                # This is a parent event
Packit Service a04d08
                records.append("Starting stage: %s" % event.get('name'))
Packit Service a04d08
                unprocessed.append(event)
Packit Service a04d08
                stages_seen.append(event.get('name'))
Packit Service a04d08
                continue
Packit Service a04d08
        else:
Packit Service a04d08
            prev_evt = unprocessed.pop()
Packit Service a04d08
            if event_name(event) == event_name(prev_evt):
Packit Service a04d08
                record = event_record(start_time, prev_evt, event)
Packit Service a04d08
                records.append(format_record("Finished stage: "
Packit Service a04d08
                                             "(%n) %d seconds",
Packit Service a04d08
                                             record) + "\n")
Packit Service a04d08
                total_time += record.get('delta')
Packit Service a04d08
            else:
Packit Service a04d08
                # not a match, put it back
Packit Service a04d08
                unprocessed.append(prev_evt)
Packit Service a04d08
Packit Service a04d08
    records.append(total_time_record(total_time))
Packit Service a04d08
    boot_records.append(records)
Packit Service a04d08
    return boot_records
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def show_events(events, print_format):
Packit Service a04d08
    '''
Packit Service a04d08
    A passthrough method that makes it easier to call generate_records()
Packit Service a04d08
Packit Service a04d08
    :param events: JSONs from dump that represents events taken from logs
Packit Service a04d08
    :param print_format: formatting to represent event, time stamp,
Packit Service a04d08
    and time taken by the event in one line
Packit Service a04d08
Packit Service a04d08
    :return: boot records ordered chronologically
Packit Service a04d08
    '''
Packit Service a04d08
    return generate_records(events, print_format=print_format)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def load_events_infile(infile):
Packit Service a04d08
    '''
Packit Service a04d08
    Takes in a log file, read it, and convert to json.
Packit Service a04d08
Packit Service a04d08
    :param infile: The Log file to be read
Packit Service a04d08
Packit Service a04d08
    :return: json version of logfile, raw file
Packit Service a04d08
    '''
Packit Service a04d08
    data = infile.read()
Packit Service a04d08
    try:
Packit Service a04d08
        return json.loads(data), data
Packit Service a04d08
    except ValueError:
Packit Service a04d08
        return None, data