#!/usr/bin/env python
"""
Gather information about a system and report it using plugins
supplied for application-specific information
"""
## sosreport.py
## gather information about a system and report it
## Copyright (C) 2006 Steve Conklin <sconklin@redhat.com>
### This program is free software; you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation; either version 2 of the License, or
## (at your option) any later version.
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
# pylint: disable-msg = W0611
# pylint: disable-msg = W0702
import sys
import os
#import curses
from optparse import OptionParser, Option
import sos.policyredhat
from sos.helpers import *
from snack import *
from threading import Thread, activeCount, enumerate
import signal
import logging
__breakHits__ = 0 # Use this to track how many times we enter the exit routine
## Set up routines to be linked to signals for termination handling
def exittermhandler(signum, frame):
doExitCode()
def doExitCode():
global __breakHits__
__breakHits__ += 1
if ( ( activeCount() > 1 ) and ( __breakHits__ == 1 ) ):
print "SIGTERM received, multiple threads detected, waiting for all threads to exit"
for thread in enumerate():
thread.wait()
print "All threads ended, cleaning up."
if ( ( activeCount() > 1 ) and ( __breakHits__ > 1 ) ):
print "Multiple SIGTERMs, multiple threads, attempting to signal threads to die immediately"
## FIXME: Add thread-kill code (see FIXME below)
print "Threads dead, cleaning up."
if ( ( activeCount() == 1 ) and ( __breakHits__ > 2 ) ):
print "Multiple SIGTERMs, single thread, exiting without cleaning up."
sys.exit(3)
# FIXME: Add code here to clean up /tmp
print "Calling sys.exit from doExitCode()"
sys.exit("Abnormal exit")
# Handle any sort of exit signal cleanly
# Currently, we intercept only sig 15 (TERM)
signal.signal(signal.SIGTERM, exittermhandler)
## FIXME: Need to figure out how to IPC with child threads in case of
## multiple SIGTERMs.
## FIXME: Need to figure out how to handle SIGKILL - we can't intercept it.
if os.getuid() != 0:
print 'You must run sosreport as root!'
sys.exit(1)
# for debugging
__raisePlugins__ = 1
class SosOption (Option):
"""Allow to specify comma delimited list of plugins"""
ACTIONS = Option.ACTIONS + ("extend",)
STORE_ACTIONS = Option.STORE_ACTIONS + ("extend",)
TYPED_ACTIONS = Option.TYPED_ACTIONS + ("extend",)
ALWAYS_TYPED_ACTIONS = Option.ALWAYS_TYPED_ACTIONS + ("extend",)
def take_action(self, action, dest, opt, value, values, parser):
if action == "extend":
lvalue = value.split(",")
values.ensure_value(dest, []).extend(lvalue)
else:
Option.take_action(self, action, dest, opt, value, values, parser)
__cmdParser__ = OptionParser(option_class=SosOption)
__cmdParser__.add_option("-a", "--alloptions", action="store_true", \
dest="usealloptions", default=False, \
help="Use all options for loaded plugins")
__cmdParser__.add_option("-f", "--fastoptions", action="store_true", \
dest="fastoptions", default=False, \
help="Use only fast options for loaded plugins")
__cmdParser__.add_option("-g", "--gatheronly", action="store_true", \
dest="gatheronly", default=False, \
help="Gather information locally but don't package or submit")
__cmdParser__.add_option("-l", "--list-plugins", action="store_true", \
dest="listPlugins", default=False, \
help="list existing plugins")
__cmdParser__.add_option("-n", "--noplugin", action="extend", \
dest="noplugins",\
help="list of plugins _not_ to load")
__cmdParser__.add_option("-o", "--onlyplugin", action="extend", \
dest="onlyplugins",\
help="list of plugins to load")
__cmdParser__.add_option("-v", "--verbose", action="count", \
dest="verbosity", \
help="How obnoxious we're being about telling the user what we're doing.")
__cmdParser__.add_option("-b", "--no-progressbar", action="store_false", \
dest="progressbar", default=True, \
help="Do not display a progress bar.")
__cmdParser__.add_option("-m", "--multithreaded", action="store_true", \
dest="multithread", \
help="Use the multithreaded information gathering mode to speed up the report (experimental)")
(__cmdLineOpts__, __cmdLineArgs__)=__cmdParser__.parse_args()
def get_curse_options(alloptions):
"""
use curses to enable the user to select some options
"""
# alloptions is an array of (plug, plugname, optname, parms(dictionary)) tuples
plugName = []
out = []
# get a sorted list of all plugin names
for rrr in alloptions:
if rrr[1] not in plugName:
plugName.append(rrr[1])
plugName.sort()
plugCbox = CheckboxTree(height=5, scroll=1)
countOpt = -1
optDic = {}
optDicCounter = 0
# iterate over all plugins with options
for curPlugName in plugName:
plugCbox.addItem(curPlugName, (snackArgs['append'],))
countOpt = countOpt+1
for opt in alloptions:
if opt[1] != curPlugName:
continue
snt = opt[2] + " ("+opt[3]['desc']+") is " + opt[3]['speed']
plugCbox.addItem(snt, (countOpt, snackArgs['append']), item = optDicCounter, selected = opt[3]['enabled'])
optDic[optDicCounter] = opt
optDicCounter += 1
screen = SnackScreen()
bb = ButtonBar(screen, (("Ok", "ok"), ("Cancel", "cancel")))
g = GridForm(screen, "Select Sosreport Options", 1, 10)
g.add(plugCbox, 0, 0)
g.add(bb, 0, 1, growx = 1)
result = g.runOnce()
screen.finish()
if bb.buttonPressed(result) == "cancel":
raise "Cancelled"
for rrr in range(0, optDicCounter):
optDic[rrr][3]['enabled'] = plugCbox.getEntryValue(rrr)[1]
out.append((optDic[rrr]))
return out
class progressBar:
def __init__(self, minValue = 0, maxValue = 10, totalWidth=12):
self.progBar = "[]" # This holds the progress bar string
self.min = minValue
self.max = maxValue
self.span = maxValue - minValue
self.width = totalWidth
self.amount = 0 # When amount == max, we are 100% done
self.updateAmount(0) # Build progress bar string
def updateAmount(self, newAmount = 0):
if newAmount < self.min: newAmount = self.min
if newAmount > self.max: newAmount = self.max
self.amount = newAmount
# Figure out the new percent done, round to an integer
diffFromMin = float(self.amount - self.min)
percentDone = (diffFromMin / float(self.span)) * 100.0
percentDone = round(percentDone)
percentDone = int(percentDone)
# Figure out how many hash bars the percentage should be
allFull = self.width - 2
numHashes = (percentDone / 100.0) * allFull
numHashes = int(round(numHashes))
# build a progress bar with hashes and spaces
self.progBar = " [" + '#'*numHashes + ' '*(allFull-numHashes) + "]"
# figure out where to put the percentage, roughly centered
percentPlace = (len(self.progBar) / 2) - len(str(percentDone))
percentString = str(percentDone) + "%"
# slice the percentage into the bar
self.progBar = " Progress" + self.progBar[0:percentPlace] + percentString + self.progBar[percentPlace+len(percentString):]
def incAmount(self, toInc = 1):
self.updateAmount(self.amount+toInc)
def finished(self):
self.updateAmount(self.max)
sys.stdout.write(self.progBar + '\n')
sys.stdout.flush()
def update(self):
sys.stdout.write(self.progBar + '\r')
sys.stdout.flush()
def sosreport():
# pylint: disable-msg = R0912
# pylint: disable-msg = R0914
# pylint: disable-msg = R0915
"""
This is the top-level function that gathers and processes all sosreport information
"""
loadedplugins = []
alloptions = []
# perhaps we should automatically locate the policy module??
policy = sos.policyredhat.SosPolicy()
# find the plugins path
paths = sys.path
for path in paths:
if path.strip()[-len("site-packages"):] == "site-packages":
pluginpath = path + "/sos/plugins"
reporterpath = path + "/sos/reporters"
# Set up common info and create destinations
dstroot = sosFindTmpDir()
cmddir = os.path.join(dstroot, "sos_commands")
logdir = os.path.join(dstroot, "sos_logs")
rptdir = os.path.join(dstroot, "sos_reports")
os.mkdir(cmddir, 0755)
os.mkdir(logdir, 0755)
os.mkdir(rptdir, 0755)
# open log file
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(levelname)s: %(message)s',
filename=logdir + "/sos.log",
filemode='w')
# define a Handler which writes INFO messages or higher to the sys.stderr
console = logging.StreamHandler()
if __cmdLineOpts__.verbosity > 0:
console.setLevel(20 - __cmdLineOpts__.verbosity)
__cmdLineOpts__.progressbar = False
else:
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter('%(message)s'))
logging.getLogger('').addHandler(console)
logging.VERBOSE = logging.INFO - 1
logging.VERBOSE2 = logging.INFO - 2
logging.VERBOSE3 = logging.INFO - 3
logging.addLevelName(logging.VERBOSE, "verbose")
logging.addLevelName(logging.VERBOSE2,"verbose2")
logging.addLevelName(logging.VERBOSE3,"verbose3")
soslog = logging.getLogger('')
# set up dict so everyone can share the following
commons = {'dstroot': dstroot, 'cmddir': cmddir, 'logdir': logdir, 'rptdir': rptdir,
'soslog': soslog, 'policy': policy, 'verbosity' : __cmdLineOpts__.verbosity}
# Make policy aware of the commons
policy.setCommons(commons)
# validate and load plugins
plugins = os.listdir(pluginpath)
if __cmdLineOpts__.listPlugins:
for plug in plugins:
try:
if ((plug[-3:] == '.py') and (plug != "__init__.py")):
plugbase = plug[:-3]
pidot = "sos.plugins." + plugbase
print plugbase
except:
print "ouch"
sys.exit()
for plug in plugins:
if ((plug[-3:] == '.py') and (plug != "__init__.py") and (not __cmdLineOpts__.noplugins or (plug[:-3] not in __cmdLineOpts__.noplugins)) and (not __cmdLineOpts__.onlyplugins or (plug[:-3] in __cmdLineOpts__.onlyplugins)) ):
try:
plugbase = plug[:-3]
pidot = "sos.plugins." + plugbase
#print "importing plugin: %s" % plugbase
try:
if policy.validatePlugin(pluginpath + plug):
pluginClass = importPlugin(pidot, plugbase)
else:
soslog.warning("Plugin %s does not validate, skipping" % plug)
continue
if not pluginClass(plugbase, commons).checkenabled():
soslog.log(logging.VERBOSE, "Plugin %s auto-disabled itself." % plug)
continue
loadedplugins.append((plugbase, pluginClass(plugbase, commons)))
except:
soslog.warning("Plugin %s does not install, skipping" % plug)
raise
except:
if __raisePlugins__:
raise
soslog.warning("plugin load failed for %s" % plug)
if not len(loadedplugins):
soslog.error("no valid plugins were enabled")
sys.exit(1)
sys.stdout.write("\n")
soslog.info("Welcome to sosreport (%d plugins loaded)" % (len(loadedplugins)))
sys.stdout.write("\n")
# Iterate over plugins for each stage
# First, gather and process options
for plugname, plug in loadedplugins:
soslog.log(logging.VERBOSE3, "processing options from plugin: %s" % plugname)
try:
len(__cmdLineOpts__.noplugins)
if plugname not in __cmdLineOpts__.noplugins:
names, parms = plug.getAllOptions()
for optname, optparm in zip(names, parms):
alloptions.append((plug, plugname, optname, optparm))
except:
names, parms = plug.getAllOptions()
for optname, optparm in zip(names, parms):
alloptions.append((plug, plugname, optname, optparm))
if not __cmdLineOpts__.fastoptions and not __cmdLineOpts__.usealloptions:
if len(alloptions):
try:
get_curse_options(alloptions)
except "Cancelled":
sys.exit("Exiting.")
else:
soslog.info("no options available for selected plugins")
elif __cmdLineOpts__.fastoptions:
for i in range(len(alloptions)):
for plug, plugname, optname, optparm in alloptions:
if optparm['speed'] == 'fast':
plug.setOption(optname, 1)
else:
plug.setOption(optname, 0)
elif __cmdLineOpts__.usealloptions:
for i in range(len(alloptions)):
for plug, plugname, optname, optparm in alloptions:
plug.setOption(optname, 1)
# Setup the progress bar
if __cmdLineOpts__.progressbar:
pbar = progressBar(0, len(loadedplugins) * 33, 40)
# Call the setup method for each plugin
for plugname, plug in loadedplugins:
soslog.log(logging.VERBOSE2, "Setting up plugin module %s" % plugname)
plug.setup()
if __cmdLineOpts__.progressbar:
pbar.incAmount()
pbar.update()
# Call the collect method for each plugin
for plugname, plug in loadedplugins:
soslog.log(logging.VERBOSE, "Executing plugin %s" % plugname)
if __cmdLineOpts__.multithread:
plug.doCollect()
else:
plug.copyStuff()
if __cmdLineOpts__.progressbar:
if __cmdLineOpts__.multithread:
pbar.incAmount()
else:
pbar.incAmount(30)
pbar.update()
# Wait for all the collection threads to exit
if __cmdLineOpts__.multithread:
for plugname, plug in loadedplugins:
soslog.log(logging.VERBOSE2, "Waiting for plugin %s to return" % plugname)
plug.wait()
if __cmdLineOpts__.progressbar:
pbar.incAmount(30)
pbar.update()
# Call the analyze method for each plugin
for plugname, plug in loadedplugins:
soslog.log(logging.VERBOSE2, "Analyzing results of plugin %s" % plugname,)
plug.analyze()
if __cmdLineOpts__.progressbar:
pbar.incAmount()
pbar.update()
if __cmdLineOpts__.progressbar:
pbar.finished()
sys.stdout.write("\n")
# Sort the module names to do the report in alphabetical order
loadedplugins.sort()
# Generate the header for the html output file
rfd = open(rptdir + "/" + "sosreport.html", "w")
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 loadedplugins:
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 loadedplugins:
html = plug.report()
rfd.write(html)
rfd.write("</body></html>")
rfd.close()
# Collect any needed user information (name, etc)
# Call the postproc method for each plugin
for plugname, plug in loadedplugins:
plug.postproc()
if __cmdLineOpts__.gatheronly:
soslog.info("Collected information is in " + dstroot)
soslog.info("Your html report is in " + rptdir + "/" + "sosreport.html")
else:
# package up the results for the support organization
policy.packageResults()
# delete gathered files
os.system("rm -rf %s" % dstroot)
# automated submission will go here
# Close all log files and perform any cleanup
logging.shutdown()
if __name__ == '__main__':
try:
sosreport()
except KeyboardInterrupt:
doExitCode()