aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sos/plugins/__init__.py53
-rw-r--r--sos/reporting.py134
-rw-r--r--sos/sosreport.py125
-rw-r--r--tests/report_tests.py73
4 files changed, 199 insertions, 186 deletions
diff --git a/sos/plugins/__init__.py b/sos/plugins/__init__.py
index e0d56d91..4e9c4cb4 100644
--- a/sos/plugins/__init__.py
+++ b/sos/plugins/__init__.py
@@ -1417,59 +1417,6 @@ class Plugin(object):
"""
pass
- def report(self):
- """ Present all information that was gathered in an html file that
- allows browsing the results.
- """
- # make this prettier
- html = u'<hr/><a name="%s"></a>\n' % self.name()
-
- # Intro
- html = html + "<h2> Plugin <em>" + self.name() + "</em></h2>\n"
-
- # Files
- if len(self.copied_files):
- html = html + "<p>Files copied:<br><ul>\n"
- for afile in self.copied_files:
- html = html + '<li><a href="%s">%s</a>' % \
- (u'..' + _to_u(afile['dstpath']), _to_u(afile['srcpath']))
- if afile['symlink'] == "yes":
- html = html + " (symlink to %s)" % _to_u(afile['pointsto'])
- html = html + '</li>\n'
- html = html + "</ul></p>\n"
-
- # Command Output
- if len(self.executed_commands):
- html = html + "<p>Commands Executed:<br><ul>\n"
- # convert file name to relative path from our root
- # don't use relpath - these are HTML paths not OS paths.
- for cmd in self.executed_commands:
- if cmd["file"] and len(cmd["file"]):
- cmd_rel_path = u"../" + _to_u(self.commons['cmddir']) \
- + "/" + _to_u(cmd['file'])
- html = html + '<li><a href="%s">%s</a></li>\n' % \
- (cmd_rel_path, _to_u(cmd['exe']))
- else:
- html = html + '<li>%s</li>\n' % (_to_u(cmd['exe']))
- html = html + "</ul></p>\n"
-
- # Alerts
- if len(self.alerts):
- html = html + "<p>Alerts:<br><ul>\n"
- for alert in self.alerts:
- html = html + '<li>%s</li>\n' % _to_u(alert)
- html = html + "</ul></p>\n"
-
- # Custom Text
- if self.custom_text != "":
- html = html + "<p>Additional Information:<br>\n"
- html = html + _to_u(self.custom_text) + "</p>\n"
-
- if six.PY2:
- return html.encode('utf8')
- else:
- return html
-
def check_process_by_name(self, process):
"""Checks if a named process is found in /proc/[0-9]*/cmdline.
Returns either True or False."""
diff --git a/sos/reporting.py b/sos/reporting.py
index cb97e473..934aaa2f 100644
--- a/sos/reporting.py
+++ b/sos/reporting.py
@@ -51,12 +51,17 @@ class Report(Node):
self.data[node.name] = node.data
+def _decode(s):
+ """returns a string text for a given unicode/str input"""
+ return (s if isinstance(s, six.text_type) else s.decode('utf8', 'ignore'))
+
+
class Section(Node):
"""A section is a container for leaf elements. Sections may be nested
inside of Report objects only."""
def __init__(self, name):
- self.name = name
+ self.name = _decode(name)
self.data = {}
def can_add(self, node):
@@ -73,9 +78,9 @@ class Command(Leaf):
ADDS_TO = "commands"
def __init__(self, name, return_code, href):
- self.data = {"name": name,
+ self.data = {"name": _decode(name),
"return_code": return_code,
- "href": href}
+ "href": _decode(href)}
class CopiedFile(Leaf):
@@ -83,16 +88,17 @@ class CopiedFile(Leaf):
ADDS_TO = "copied_files"
def __init__(self, name, href):
- self.data = {"name": name,
- "href": href}
+ self.data = {"name": _decode(name),
+ "href": _decode(href)}
class CreatedFile(Leaf):
ADDS_TO = "created_files"
- def __init__(self, name):
- self.data = {"name": name}
+ def __init__(self, name, href):
+ self.data = {"name": _decode(name),
+ "href": _decode(href)}
class Alert(Leaf):
@@ -100,7 +106,7 @@ class Alert(Leaf):
ADDS_TO = "alerts"
def __init__(self, content):
- self.data = content
+ self.data = _decode(content)
class Note(Leaf):
@@ -108,7 +114,7 @@ class Note(Leaf):
ADDS_TO = "notes"
def __init__(self, content):
- self.data = content
+ self.data = _decode(content)
def ends_bs(string):
@@ -125,32 +131,60 @@ def ends_bs(string):
class PlainTextReport(object):
"""Will generate a plain text report from a top_level Report object"""
+ HEADER = ""
+ FOOTER = ""
LEAF = " * %(name)s"
ALERT = " ! %s"
NOTE = " * %s"
- DIVIDER = "=" * 72
+ PLUGLISTHEADER = "Loaded Plugins:"
+ PLUGLISTITEM = " {name}"
+ PLUGLISTSEP = "\n"
+ PLUGLISTMAXITEMS = 5
+ PLUGLISTFOOTER = ""
+ PLUGINFORMAT = "{name}"
+ PLUGDIVIDER = "=" * 72
subsections = (
- (Command, LEAF, "- commands executed:"),
- (CopiedFile, LEAF, "- files copied:"),
- (CreatedFile, LEAF, "- files created:"),
- (Alert, ALERT, "- alerts:"),
- (Note, NOTE, "- notes:"),
+ (Command, LEAF, "- commands executed:", ""),
+ (CopiedFile, LEAF, "- files copied:", ""),
+ (CreatedFile, LEAF, "- files created:", ""),
+ (Alert, ALERT, "- alerts:", ""),
+ (Note, NOTE, "- notes:", ""),
)
line_buf = []
def __init__(self, report_node):
- self.report_node = report_node
+ self.report_data = sorted(six.iteritems(report_node.data))
def unicode(self):
self.line_buf = line_buf = []
- for section_name, section_contents in sorted(six.iteritems(
- self.report_node.data)):
- line_buf.append(section_name + "\n" + self.DIVIDER)
- for type_, format_, header in self.subsections:
+
+ if (len(self.HEADER) > 0):
+ line_buf.append(self.HEADER)
+
+ # generate section/plugin list, split long list to multiple lines
+ line_buf.append(self.PLUGLISTHEADER)
+ line = ""
+ i = 0
+ plugcount = len(self.report_data)
+ for section_name, _ in self.report_data:
+ line += self.PLUGLISTITEM.format(name=section_name)
+ i += 1
+ if (i % self.PLUGLISTMAXITEMS == 0) and (i < plugcount):
+ line += self.PLUGLISTSEP
+ line += self.PLUGLISTFOOTER
+ line_buf.append(line)
+
+ for section_name, section_contents in self.report_data:
+ line_buf.append(self.PLUGDIVIDER)
+ line_buf.append(self.PLUGINFORMAT.format(name=section_name))
+ for type_, format_, header, footer in self.subsections:
self.process_subsection(section_contents, type_.ADDS_TO,
- header, format_)
+ header, format_, footer)
+
+ if (len(self.FOOTER) > 0):
+ line_buf.append(self.FOOTER)
# Workaround python.six mishandling of strings ending in '/' by
# adding a single space following any '\' at end-of-line.
@@ -158,16 +192,70 @@ class PlainTextReport(object):
line_buf = [line + " " if ends_bs(line) else line for line in line_buf]
output = u'\n'.join(map(lambda i: (i if isinstance(i, six.text_type)
- else six.u(i)), line_buf))
+ else i.decode('utf8', 'ignore')),
+ line_buf))
if six.PY3:
return output
else:
return output.encode('utf8')
- def process_subsection(self, section, key, header, format_):
+ def process_subsection(self, section, key, header, format_, footer):
if key in section:
self.line_buf.append(header)
for item in section.get(key):
self.line_buf.append(format_ % item)
+ if (len(footer) > 0):
+ self.line_buf.append(footer)
+
+
+class HTMLReport(PlainTextReport):
+ """Will generate a HTML report from a top_level Report object"""
+
+ HEADER = """<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+ <head>
+ <link rel="stylesheet" type="text/css" media="screen"
+ href="donot.css" />
+ <meta http-equiv="Content-Type" content="text/html;
+ charset=utf-8" />
+ <title>Sos System Report</title>
+ <style>
+ td {
+ padding: 0 5px;
+ }
+ </style>
+ </head>
+ <body>\n"""
+ FOOTER = "</body></html>"
+ LEAF = '<li><a href="%(href)s">%(name)s</a></li>'
+ ALERT = "<li>%s</li>"
+ NOTE = "<li>%s</li>"
+ PLUGLISTHEADER = "<h3>Loaded Plugins:</h3><table><tr>"
+ PLUGLISTITEM = '<td><a href="#{name}">{name}</a></td>\n'
+ PLUGLISTSEP = "</tr>\n<tr>"
+ PLUGLISTMAXITEMS = 5
+ PLUGLISTFOOTER = "</tr></table>"
+ PLUGINFORMAT = '<a name="{name}"></a><h2>Plugin <em>{name}</em></h2>'
+ PLUGDIVIDER = "<hr/>\n"
+
+ subsections = (
+ (Command, LEAF, "<p>Commands executed:<br><ul>", "</ul></p>"),
+ (CopiedFile, LEAF, "<p>Files copied:<br><ul>", "</ul></p>"),
+ (CreatedFile, LEAF, "<p>Files created:<br><ul>", "</ul></p>"),
+ (Alert, ALERT, "<p>Alerts:<br><ul>", "</ul></p>"),
+ (Note, NOTE, "<p>Notes:<br><ul>", "</ul></p>"),
+ )
+
+
+class JSONReport(PlainTextReport):
+ """Will generate a JSON report from a top_level Report object"""
+
+ def unicode(self):
+ output = json.dumps(self.report_data, indent=4, ensure_ascii=False)
+ if six.PY3:
+ return output
+ else:
+ return output.encode('utf8')
# vim: set et ts=4 sw=4 :
diff --git a/sos/sosreport.py b/sos/sosreport.py
index ad826ddd..11cae530 100644
--- a/sos/sosreport.py
+++ b/sos/sosreport.py
@@ -37,7 +37,8 @@ from sos import _arg_defaults, SoSOptions
import sos.policies
from sos.archive import TarFileArchive
from sos.reporting import (Report, Section, Command, CopiedFile, CreatedFile,
- Alert, Note, PlainTextReport)
+ Alert, Note, PlainTextReport, JSONReport,
+ HTMLReport)
# PYCOMPAT
import six
@@ -1103,9 +1104,10 @@ class SoSReport(object):
]) + '\n'
self.archive.add_string(env, 'environment')
- def plain_report(self):
+ def generate_reports(self):
report = Report()
+ # generate report content
for plugname, plug in self.loaded_plugins:
section = Section(name=plugname)
@@ -1121,97 +1123,43 @@ class SoSReport(object):
for cmd in plug.executed_commands:
section.add(Command(name=cmd['exe'], return_code=0,
- href="../" + cmd['file']))
+ href=os.path.join(
+ "..",
+ self.get_commons()['cmddir'],
+ cmd['file']
+ )))
for content, f in plug.copy_strings:
- section.add(CreatedFile(name=f))
+ section.add(CreatedFile(name=f,
+ href=os.path.join(
+ "..",
+ "sos_strings",
+ plugname,
+ f)))
report.add(section)
- try:
- fd = self.get_temp_file()
- output = PlainTextReport(report).unicode()
- fd.write(output)
- fd.flush()
- self.archive.add_file(fd, dest=os.path.join('sos_reports',
- 'sos.txt'))
- except (OSError, IOError) as e:
- if e.errno in fatal_fs_errors:
- self.ui_log.error("")
- self.ui_log.error(" %s while writing text report"
- % e.strerror)
- self.ui_log.error("")
- self._exit(1)
- def html_report(self):
- try:
- self._html_report()
- except (OSError, IOError) as e:
- if e.errno in fatal_fs_errors:
- self.ui_log.error("")
- self.ui_log.error(" %s while writing HTML report"
- % e.strerror)
- self.ui_log.error("")
- self._exit(1)
-
- def _html_report(self):
- # Generate the header for the html output file
- rfd = self.get_temp_file()
- rfd.write("""
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
- "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
- <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
- <head>
- <link rel="stylesheet" type="text/css" media="screen"
- href="donot.css" />
- <meta http-equiv="Content-Type" content="text/html;
- charset=utf-8" />
- <title>Sos System Report</title>
- </head>
- <body>
- """)
-
- # Make a pass to gather Alerts and a list of module names
- allAlerts = []
- plugNames = []
- for plugname, plug in self.loaded_plugins:
- for alert in plug.alerts:
- allAlerts.append('<a href="#%s">%s</a>: %s' % (plugname,
- plugname,
- alert))
- plugNames.append(plugname)
-
- # Create a table of links to the module info
- rfd.write("<hr/><h3>Loaded Plugins:</h3>")
- rfd.write("<table><tr>\n")
- rr = 0
- for i in range(len(plugNames)):
- rfd.write('<td><a href="#%s">%s</a></td>\n' % (plugNames[i],
- plugNames[i]))
- rr = divmod(i, 4)[1]
- if (rr == 3):
- rfd.write('</tr>')
- if not (rr == 3):
- rfd.write('</tr>')
- rfd.write('</table>\n')
-
- rfd.write('<hr/><h3>Alerts:</h3>')
- rfd.write('<ul>')
- for alert in allAlerts:
- rfd.write('<li>%s</li>' % alert)
- rfd.write('</ul>')
-
- # Call the report method for each plugin
- for plugname, plug in self.loaded_plugins:
+ # print it in text, JSON and HTML formats
+ formatlist = (
+ (PlainTextReport, "sos.txt", "text"),
+ (JSONReport, "sos.json", "JSON"),
+ (HTMLReport, "sos.html", "HTML")
+ )
+ for class_, filename, type_ in formatlist:
try:
- html = plug.report()
- except Exception:
- self.handle_exception()
- else:
- rfd.write(html)
- rfd.write("</body></html>")
- rfd.flush()
- self.archive.add_file(rfd, dest=os.path.join('sos_reports',
- 'sos.html'))
+ fd = self.get_temp_file()
+ output = class_(report).unicode()
+ fd.write(output)
+ fd.flush()
+ self.archive.add_file(fd, dest=os.path.join('sos_reports',
+ filename))
+ except (OSError, IOError) as e:
+ if e.errno in fatal_fs_errors:
+ self.ui_log.error("")
+ self.ui_log.error(" %s while writing %s report"
+ % (e.strerror, type_))
+ self.ui_log.error("")
+ self._exit(1)
def postproc(self):
for plugname, plug in self.loaded_plugins:
@@ -1399,8 +1347,7 @@ class SoSReport(object):
if not self.opts.no_env_vars:
self.collect_env_vars()
if not self.opts.noreport:
- self.html_report()
- self.plain_report()
+ self.generate_reports()
self.postproc()
self.version()
return self.final_work()
diff --git a/tests/report_tests.py b/tests/report_tests.py
index dd390669..e18c4cf4 100644
--- a/tests/report_tests.py
+++ b/tests/report_tests.py
@@ -8,8 +8,9 @@ try:
except ImportError:
import simplejson as json
-from sos.reporting import Report, Section, Command, CopiedFile, CreatedFile, Alert
-from sos.reporting import PlainTextReport
+from sos.reporting import (Report, Section, Command, CopiedFile, CreatedFile,
+ Alert, PlainTextReport)
+
class ReportTest(unittest.TestCase):
@@ -38,22 +39,23 @@ class ReportTest(unittest.TestCase):
report.add(section2)
expected = json.dumps({"section": {},
- "section2": {},})
+ "section2": {}, })
self.assertEquals(expected, str(report))
-
def test_deeply_nested(self):
report = Report()
section = Section(name="section")
- command = Command(name="a command", return_code=0, href="does/not/matter")
+ command = Command(name="a command", return_code=0,
+ href="does/not/matter")
section.add(command)
report.add(section)
- expected = json.dumps({"section": {"commands": [{"name": "a command",
- "return_code": 0,
- "href": "does/not/matter"}]}})
+ expected = json.dumps({"section": {
+ "commands": [{"name": "a command",
+ "return_code": 0,
+ "href": "does/not/matter"}]}})
self.assertEquals(expected, str(report))
@@ -63,22 +65,37 @@ class TestPlainReport(unittest.TestCase):
def setUp(self):
self.report = Report()
self.section = Section(name="plugin")
- self.div = PlainTextReport.DIVIDER
+ self.div = '\n' + PlainTextReport.PLUGDIVIDER
+ self.pluglist = "Loaded Plugins:\n{pluglist}"
+ self.defaultheader = u''.join([
+ self.pluglist.format(pluglist=" plugin"),
+ self.div,
+ "\nplugin\n"
+ ])
def test_basic(self):
- self.assertEquals("", PlainTextReport(self.report).unicode())
+ self.assertEquals(self.pluglist.format(pluglist=""),
+ PlainTextReport(self.report).unicode())
def test_one_section(self):
self.report.add(self.section)
- self.assertEquals("plugin\n" + self.div, PlainTextReport(self.report).unicode())
+ self.assertEquals(self.defaultheader,
+ PlainTextReport(self.report).unicode() + '\n')
def test_two_sections(self):
section1 = Section(name="first")
section2 = Section(name="second")
self.report.add(section1, section2)
- self.assertEquals("first\n" + self.div + "\nsecond\n" + self.div, PlainTextReport(self.report).unicode())
+ self.assertEquals(u''.join([
+ self.pluglist.format(pluglist=" first second"),
+ self.div,
+ "\nfirst",
+ self.div,
+ "\nsecond"
+ ]),
+ PlainTextReport(self.report).unicode())
def test_command(self):
cmd = Command(name="ls -al /foo/bar/baz",
@@ -87,32 +104,46 @@ class TestPlainReport(unittest.TestCase):
self.section.add(cmd)
self.report.add(self.section)
- self.assertEquals("plugin\n" + self.div + "\n- commands executed:\n * ls -al /foo/bar/baz",
- PlainTextReport(self.report).unicode())
+ self.assertEquals(u''.join([
+ self.defaultheader,
+ "- commands executed:\n * ls -al /foo/bar/baz"
+ ]),
+ PlainTextReport(self.report).unicode())
def test_copied_file(self):
cf = CopiedFile(name="/etc/hosts", href="etc/hosts")
self.section.add(cf)
self.report.add(self.section)
- self.assertEquals("plugin\n" + self.div + "\n- files copied:\n * /etc/hosts",
- PlainTextReport(self.report).unicode())
+ self.assertEquals(u''.join([
+ self.defaultheader,
+ "- files copied:\n * /etc/hosts"
+ ]),
+ PlainTextReport(self.report).unicode())
def test_created_file(self):
- crf = CreatedFile(name="sample.txt")
+ crf = CreatedFile(name="sample.txt",
+ href="../sos_strings/sample/sample.txt")
self.section.add(crf)
self.report.add(self.section)
- self.assertEquals("plugin\n" + self.div + "\n- files created:\n * sample.txt",
- PlainTextReport(self.report).unicode())
+ self.assertEquals(u''.join([
+ self.defaultheader,
+ "- files created:\n * sample.txt"
+ ]),
+ PlainTextReport(self.report).unicode())
def test_alert(self):
alrt = Alert("this is an alert")
self.section.add(alrt)
self.report.add(self.section)
- self.assertEquals("plugin\n" + self.div + "\n- alerts:\n ! this is an alert",
- PlainTextReport(self.report).unicode())
+ self.assertEquals(u''.join([
+ self.defaultheader,
+ "- alerts:\n ! this is an alert"
+ ]),
+ PlainTextReport(self.report).unicode())
+
if __name__ == "__main__":
unittest.main()