Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ matrix:
env: TOXENV=flake8
- python: 3.6
env: TOXENV=pylint
- python: 3.6
env: TOXENV=black
- python: 3.6
env: TOXENV=isort
- python: 3.7
env: TOXENV=doc
# Test all supported Python versions (but at the end, so we schedule
Expand Down
5 changes: 5 additions & 0 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Before any pull request can be accepted, you must do the following:
`tools/.github-cla-signers`_
* Add or update any `unit tests`_ accordingly
* Add or update any `integration tests`_ (if applicable)
* Format code (using black and isort) with `tox -e format`
* Ensure unit tests and linting pass using `tox`_
* Submit a PR against the `main` branch of the `cloud-init` repository

Expand Down Expand Up @@ -133,6 +134,10 @@ Do these things for each feature or bug

git commit

* Apply black and isort formatting rules with `tox`_::

tox -e format

* Run unit tests and lint/formatting checks with `tox`_::

tox
Expand Down
269 changes: 165 additions & 104 deletions cloudinit/analyze/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,62 +5,111 @@
import argparse
import re
import sys
from datetime import datetime

from cloudinit.util import json_dumps
from datetime import datetime
from . import dump
from . import show

from . import dump, show


def get_parser(parser=None):
if not parser:
parser = argparse.ArgumentParser(
prog='cloudinit-analyze',
description='Devel tool: Analyze cloud-init logs and data')
subparsers = parser.add_subparsers(title='Subcommands', dest='subcommand')
prog="cloudinit-analyze",
description="Devel tool: Analyze cloud-init logs and data",
)
subparsers = parser.add_subparsers(title="Subcommands", dest="subcommand")
subparsers.required = True

parser_blame = subparsers.add_parser(
'blame', help='Print list of executed stages ordered by time to init')
"blame", help="Print list of executed stages ordered by time to init"
)
parser_blame.add_argument(
'-i', '--infile', action='store', dest='infile',
default='/var/log/cloud-init.log',
help='specify where to read input.')
"-i",
"--infile",
action="store",
dest="infile",
default="/var/log/cloud-init.log",
help="specify where to read input.",
)
parser_blame.add_argument(
'-o', '--outfile', action='store', dest='outfile', default='-',
help='specify where to write output. ')
parser_blame.set_defaults(action=('blame', analyze_blame))
"-o",
"--outfile",
action="store",
dest="outfile",
default="-",
help="specify where to write output. ",
)
parser_blame.set_defaults(action=("blame", analyze_blame))

parser_show = subparsers.add_parser(
'show', help='Print list of in-order events during execution')
parser_show.add_argument('-f', '--format', action='store',
dest='print_format', default='%I%D @%Es +%ds',
help='specify formatting of output.')
parser_show.add_argument('-i', '--infile', action='store',
dest='infile', default='/var/log/cloud-init.log',
help='specify where to read input.')
parser_show.add_argument('-o', '--outfile', action='store',
dest='outfile', default='-',
help='specify where to write output.')
parser_show.set_defaults(action=('show', analyze_show))
"show", help="Print list of in-order events during execution"
)
parser_show.add_argument(
"-f",
"--format",
action="store",
dest="print_format",
default="%I%D @%Es +%ds",
help="specify formatting of output.",
)
parser_show.add_argument(
"-i",
"--infile",
action="store",
dest="infile",
default="/var/log/cloud-init.log",
help="specify where to read input.",
)
parser_show.add_argument(
"-o",
"--outfile",
action="store",
dest="outfile",
default="-",
help="specify where to write output.",
)
parser_show.set_defaults(action=("show", analyze_show))
parser_dump = subparsers.add_parser(
'dump', help='Dump cloud-init events in JSON format')
parser_dump.add_argument('-i', '--infile', action='store',
dest='infile', default='/var/log/cloud-init.log',
help='specify where to read input. ')
parser_dump.add_argument('-o', '--outfile', action='store',
dest='outfile', default='-',
help='specify where to write output. ')
parser_dump.set_defaults(action=('dump', analyze_dump))
"dump", help="Dump cloud-init events in JSON format"
)
parser_dump.add_argument(
"-i",
"--infile",
action="store",
dest="infile",
default="/var/log/cloud-init.log",
help="specify where to read input. ",
)
parser_dump.add_argument(
"-o",
"--outfile",
action="store",
dest="outfile",
default="-",
help="specify where to write output. ",
)
parser_dump.set_defaults(action=("dump", analyze_dump))
parser_boot = subparsers.add_parser(
'boot', help='Print list of boot times for kernel and cloud-init')
parser_boot.add_argument('-i', '--infile', action='store',
dest='infile', default='/var/log/cloud-init.log',
help='specify where to read input. ')
parser_boot.add_argument('-o', '--outfile', action='store',
dest='outfile', default='-',
help='specify where to write output.')
parser_boot.set_defaults(action=('boot', analyze_boot))
"boot", help="Print list of boot times for kernel and cloud-init"
)
parser_boot.add_argument(
"-i",
"--infile",
action="store",
dest="infile",
default="/var/log/cloud-init.log",
help="specify where to read input. ",
)
parser_boot.add_argument(
"-o",
"--outfile",
action="store",
dest="outfile",
default="-",
help="specify where to write output.",
)
parser_boot.set_defaults(action=("boot", analyze_boot))
return parser


Expand All @@ -78,61 +127,68 @@ def analyze_boot(name, args):
"""
infh, outfh = configure_io(args)
kernel_info = show.dist_check_timestamp()
status_code, kernel_start, kernel_end, ci_sysd_start = \
kernel_info
status_code, kernel_start, kernel_end, ci_sysd_start = kernel_info
kernel_start_timestamp = datetime.utcfromtimestamp(kernel_start)
kernel_end_timestamp = datetime.utcfromtimestamp(kernel_end)
ci_sysd_start_timestamp = datetime.utcfromtimestamp(ci_sysd_start)
try:
last_init_local = \
[e for e in _get_events(infh) if e['name'] == 'init-local' and
'starting search' in e['description']][-1]
ci_start = datetime.utcfromtimestamp(last_init_local['timestamp'])
last_init_local = [
e
for e in _get_events(infh)
if e["name"] == "init-local"
and "starting search" in e["description"]
][-1]
ci_start = datetime.utcfromtimestamp(last_init_local["timestamp"])
except IndexError:
ci_start = 'Could not find init-local log-line in cloud-init.log'
ci_start = "Could not find init-local log-line in cloud-init.log"
status_code = show.FAIL_CODE

FAILURE_MSG = 'Your Linux distro or container does not support this ' \
'functionality.\n' \
'You must be running a Kernel Telemetry supported ' \
'distro.\nPlease check ' \
'https://cloudinit.readthedocs.io/en/latest' \
'/topics/analyze.html for more ' \
'information on supported distros.\n'

SUCCESS_MSG = '-- Most Recent Boot Record --\n' \
' Kernel Started at: {k_s_t}\n' \
' Kernel ended boot at: {k_e_t}\n' \
' Kernel time to boot (seconds): {k_r}\n' \
' Cloud-init activated by systemd at: {ci_sysd_t}\n' \
' Time between Kernel end boot and Cloud-init ' \
'activation (seconds): {bt_r}\n' \
' Cloud-init start: {ci_start}\n'

CONTAINER_MSG = '-- Most Recent Container Boot Record --\n' \
' Container started at: {k_s_t}\n' \
' Cloud-init activated by systemd at: {ci_sysd_t}\n' \
' Cloud-init start: {ci_start}\n' \

FAILURE_MSG = (
"Your Linux distro or container does not support this "
"functionality.\n"
"You must be running a Kernel Telemetry supported "
"distro.\nPlease check "
"https://cloudinit.readthedocs.io/en/latest"
"/topics/analyze.html for more "
"information on supported distros.\n"
)

SUCCESS_MSG = (
"-- Most Recent Boot Record --\n"
" Kernel Started at: {k_s_t}\n"
" Kernel ended boot at: {k_e_t}\n"
" Kernel time to boot (seconds): {k_r}\n"
" Cloud-init activated by systemd at: {ci_sysd_t}\n"
" Time between Kernel end boot and Cloud-init "
"activation (seconds): {bt_r}\n"
" Cloud-init start: {ci_start}\n"
)

CONTAINER_MSG = (
"-- Most Recent Container Boot Record --\n"
" Container started at: {k_s_t}\n"
" Cloud-init activated by systemd at: {ci_sysd_t}\n"
" Cloud-init start: {ci_start}\n"
)
status_map = {
show.FAIL_CODE: FAILURE_MSG,
show.CONTAINER_CODE: CONTAINER_MSG,
show.SUCCESS_CODE: SUCCESS_MSG
show.SUCCESS_CODE: SUCCESS_MSG,
}

kernel_runtime = kernel_end - kernel_start
between_process_runtime = ci_sysd_start - kernel_end

kwargs = {
'k_s_t': kernel_start_timestamp,
'k_e_t': kernel_end_timestamp,
'k_r': kernel_runtime,
'bt_r': between_process_runtime,
'k_e': kernel_end,
'k_s': kernel_start,
'ci_sysd': ci_sysd_start,
'ci_sysd_t': ci_sysd_start_timestamp,
'ci_start': ci_start
"k_s_t": kernel_start_timestamp,
"k_e_t": kernel_end_timestamp,
"k_r": kernel_runtime,
"bt_r": between_process_runtime,
"k_e": kernel_end,
"k_s": kernel_start,
"ci_sysd": ci_sysd_start,
"ci_sysd_t": ci_sysd_start_timestamp,
"ci_start": ci_start,
}

outfh.write(status_map[status_code].format(**kwargs))
Expand All @@ -152,15 +208,16 @@ def analyze_blame(name, args):
and sorting by record data ('delta')
"""
(infh, outfh) = configure_io(args)
blame_format = ' %ds (%n)'
r = re.compile(r'(^\s+\d+\.\d+)', re.MULTILINE)
for idx, record in enumerate(show.show_events(_get_events(infh),
blame_format)):
blame_format = " %ds (%n)"
r = re.compile(r"(^\s+\d+\.\d+)", re.MULTILINE)
for idx, record in enumerate(
show.show_events(_get_events(infh), blame_format)
):
srecs = sorted(filter(r.match, record), reverse=True)
outfh.write('-- Boot Record %02d --\n' % (idx + 1))
outfh.write('\n'.join(srecs) + '\n')
outfh.write('\n')
outfh.write('%d boot records analyzed\n' % (idx + 1))
outfh.write("-- Boot Record %02d --\n" % (idx + 1))
outfh.write("\n".join(srecs) + "\n")
outfh.write("\n")
outfh.write("%d boot records analyzed\n" % (idx + 1))


def analyze_show(name, args):
Expand All @@ -184,21 +241,25 @@ def analyze_show(name, args):
Finished stage: (modules-final) 0.NNN seconds
"""
(infh, outfh) = configure_io(args)
for idx, record in enumerate(show.show_events(_get_events(infh),
args.print_format)):
outfh.write('-- Boot Record %02d --\n' % (idx + 1))
outfh.write('The total time elapsed since completing an event is'
' printed after the "@" character.\n')
outfh.write('The time the event takes is printed after the "+" '
'character.\n\n')
outfh.write('\n'.join(record) + '\n')
outfh.write('%d boot records analyzed\n' % (idx + 1))
for idx, record in enumerate(
show.show_events(_get_events(infh), args.print_format)
):
outfh.write("-- Boot Record %02d --\n" % (idx + 1))
outfh.write(
"The total time elapsed since completing an event is"
' printed after the "@" character.\n'
)
outfh.write(
'The time the event takes is printed after the "+" character.\n\n'
)
outfh.write("\n".join(record) + "\n")
outfh.write("%d boot records analyzed\n" % (idx + 1))


def analyze_dump(name, args):
"""Dump cloud-init events in json format"""
(infh, outfh) = configure_io(args)
outfh.write(json_dumps(_get_events(infh)) + '\n')
outfh.write(json_dumps(_get_events(infh)) + "\n")


def _get_events(infile):
Expand All @@ -211,28 +272,28 @@ def _get_events(infile):

def configure_io(args):
"""Common parsing and setup of input/output files"""
if args.infile == '-':
if args.infile == "-":
infh = sys.stdin
else:
try:
infh = open(args.infile, 'r')
infh = open(args.infile, "r")
except OSError:
sys.stderr.write('Cannot open file %s\n' % args.infile)
sys.stderr.write("Cannot open file %s\n" % args.infile)
sys.exit(1)

if args.outfile == '-':
if args.outfile == "-":
outfh = sys.stdout
else:
try:
outfh = open(args.outfile, 'w')
outfh = open(args.outfile, "w")
except OSError:
sys.stderr.write('Cannot open file %s\n' % args.outfile)
sys.stderr.write("Cannot open file %s\n" % args.outfile)
sys.exit(1)

return (infh, outfh)


if __name__ == '__main__':
if __name__ == "__main__":
parser = get_parser()
args = parser.parse_args()
(name, action_functor) = args.action
Expand Down
Loading