diff options
-rwxr-xr-x | rteval-cmd | 311 | ||||
-rw-r--r-- | rteval/__init__.py | 243 | ||||
-rw-r--r-- | rteval/modules/__init__.py | 8 | ||||
-rw-r--r-- | rteval/modules/loads/__init__.py | 6 | ||||
-rw-r--r-- | rteval/modules/loads/hackbench.py | 4 | ||||
-rw-r--r-- | rteval/modules/loads/kcompile.py | 4 | ||||
-rw-r--r-- | rteval/modules/measurement/__init__.py | 2 | ||||
-rw-r--r-- | rteval/modules/measurement/cyclictest.py | 4 | ||||
-rw-r--r-- | rteval/rteval.py | 483 | ||||
-rw-r--r-- | rteval/sysinfo/__init__.py | 2 | ||||
-rw-r--r-- | rteval/sysinfo/dmi.py | 2 | ||||
-rw-r--r-- | rteval/sysinfo/kernel.py | 4 | ||||
-rw-r--r-- | rteval/sysinfo/osinfo.py | 2 | ||||
-rw-r--r-- | rteval/sysinfo/services.py | 4 |
14 files changed, 573 insertions, 506 deletions
diff --git a/rteval-cmd b/rteval-cmd new file mode 100755 index 0000000..8d1e30d --- /dev/null +++ b/rteval-cmd @@ -0,0 +1,311 @@ +#!/usr/bin/python -tt +# +# rteval - script for evaluating platform suitability for RT Linux +# +# This program is used to determine the suitability of +# a system for use in a Real Time Linux environment. +# It starts up various system loads and measures event +# latency while the loads are running. A report is generated +# to show the latencies encountered during the run. +# +# Copyright 2009,2010,2011,2012 Clark Williams <williams@redhat.com> +# Copyright 2009,2010,2011,2012 David Sommerseth <davids@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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# For the avoidance of doubt the "preferred form" of this code is one which +# is in an open unpatent encumbered format. Where cryptographic key signing +# forms part of the process of creating an executable the information +# including keys needed to generate an equivalently functional executable +# are deemed to be part of the source code. +# + +import sys, os, time, optparse, tempfile +import libxml2, libxslt +from datetime import datetime +from rteval.Log import Log +from rteval import RtEval, rtevalConfig +from rteval.modules.loads import LoadModules +from rteval.modules.measurement import MeasurementModules + + + +def summarize(repfile, xslt): + isarchive = False + summaryfile = repfile + if repfile.endswith(".tar.bz2"): + import tarfile + try: + t = tarfile.open(repfile) + except: + print "Don't know how to summarize %s (tarfile open failed)" % repfile + return + element = None + for f in t.getnames(): + if f.find('summary.xml') != -1: + element = f + break + if element == None: + print "No summary.xml found in tar archive %s" % repfile + return + tmp = tempfile.gettempdir() + t.extract(element, path=tmp) + summaryfile = os.path.join(tmp, element) + isarchive = True + + # Load the XSLT template + xsltdoc = libxml2.parseFile(xslt) + xsltprs = libxslt.parseStylesheetDoc(xsltdoc) + + # Load the summay.xml report - with some simple sanity checks + xmldoc = libxml2.parseFile(summaryfile) + if xmldoc.name != summaryfile: + raise RuntimeError("Failed to load the report") + if xmldoc.children.name != 'rteval': + raise RuntimeError("The report doesn't seem like a rteval summary report") + + # Parse the report through the XSLT template - preserve proper encoding + resdoc = xsltprs.applyStylesheet(xmldoc, None) + report = xsltprs.saveResultToString(resdoc).encode(xsltprs.encoding()) + + # Dump the report to stdout - as UTF-8 + print report.encode('UTF-8') + + # Clean up + del report + del resdoc + del xmldoc + del xsltprs + del xsltdoc + + if isarchive: + os.unlink(summaryfile) + + + +def parse_options(cfg, cmdargs): + '''parse the command line arguments''' + + rtevcfg = cfg.GetSection('rteval') + parser = optparse.OptionParser() + parser.add_option("-d", "--duration", dest="duration", + type="string", default=rtevcfg.duration, + help="specify length of test run (default: %default)") + parser.add_option("-v", "--verbose", dest="verbose", + action="store_true", default=rtevcfg.verbose, + help="turn on verbose prints (default: %default)") + parser.add_option("-w", "--workdir", dest="workdir", + type="string", default=rtevcfg.workdir, + help="top directory for rteval data (default: %default)") + parser.add_option("-l", "--loaddir", dest="srcdir", + type="string", default=rtevcfg.srcdir, + help="directory for load source tarballs (default: %default)") + parser.add_option("-i", "--installdir", dest="installdir", + type="string", default=rtevcfg.installdir, + help="place to locate installed templates (default: %default)") + parser.add_option("-s", "--sysreport", dest="sysreport", + action="store_true", default=rtevcfg.sysreport, + help='run sysreport to collect system data (default: %default)') + parser.add_option("-D", '--debug', dest='debugging', + action='store_true', default=rtevcfg.debugging, + help='turn on debug prints (default: %default)') + parser.add_option("-X", '--xmlrpc-submit', dest='xmlrpc', + action='store', default=rtevcfg.xmlrpc, metavar='HOST', + help='Hostname to XML-RPC server to submit reports') + parser.add_option("-P", "--xmlrpc-no-abort", dest="xmlrpc_noabort", + action='store_true', default=False, + help="Do not abort if XML-RPC server do not respond to ping request"); + parser.add_option("-Z", '--summarize', dest='summarize', + action='store_true', default=False, + help='summarize an already existing XML report') + parser.add_option("-H", '--raw-histogram', dest='rawhistogram', + action='store_true', default=False, + help='Generate raw histogram data for an already existing XML report') + parser.add_option("-f", "--inifile", dest="inifile", + type='string', default=None, + help="initialization file for configuring loads and behavior") + parser.add_option("-a", "--annotate", dest="annotate", + type="string", default=None, + help="Add a little annotation which is stored in the report") + parser.add_option("-L", "--logging", dest="logging", + action='store_true', default=False, + help='log the output of the loads in the report directory') + parser.add_option("-O", "--onlyload", dest="onlyload", + action='store_true', default=False, + help="only run the loads (don't run measurement threads)") + + (cmd_opts, cmd_args) = parser.parse_args(args = cmdargs) + if cmd_opts.duration: + mult = 1.0 + v = cmd_opts.duration.lower() + if v.endswith('s'): + v = v[:-1] + elif v.endswith('m'): + v = v[:-1] + mult = 60.0 + elif v.endswith('h'): + v = v[:-1] + mult = 3600.0 + elif v.endswith('d'): + v = v[:-1] + mult = 3600.0 * 24.0 + cmd_opts.duration = float(v) * mult + return (cmd_opts, cmd_args) + + + +if __name__ == '__main__': + from rteval.sysinfo import dmi + + dmi.ProcessWarnings() + + try: + default_config = { + 'rteval': { + 'quiet' : False, + 'verbose' : False, + 'keepdata' : True, + 'debugging' : False, + 'duration' : '60', + 'sysreport' : False, + 'reportdir' : None, + 'reportfile' : None, + 'workdir' : os.getcwd(), + 'installdir' : '/usr/share/rteval', + 'srcdir' : '/usr/share/rteval/loadsource', + 'xmlrpc' : None, + 'xslt_report': '/usr/share/rteval/rteval_text.xsl', + 'report_interval': '600', + 'logging' : False, + }, + 'loads' : { + 'kcompile' : 'module', + 'hackbench' : 'module', + }, + 'kcompile' : { + 'source' : 'linux-2.6.21.tar.bz2', + 'jobspercore': '2', + }, + 'hackbench' : { + 'source' : 'hackbench.tar.bz2', + 'jobspercore': '5', + }, + 'cyclictest' : { + 'interval' : '100', + 'buckets' : '2000', + 'distance' : '25', + }, + 'measurement' : { + 'cyclictest' : 'module', + } + } + + # Prepare logging + logger = Log() + logger.SetLogVerbosity(Log.INFO) + + # setup initial configuration + config = rtevalConfig.rtevalConfig(default_config, logger=logger) + + # Before really parsing options, see if we have been given a config file in the args + # and load it - just so that default values are according to the config file + try: + cfgfile = sys.argv[sys.argv.index('-f')+1] + config.Load(cfgfile) + except IndexError: + # Missing file argument + raise RuntimeError('The -f option requires a file name to the configuration file') + except ValueError: + # No configuration file given + pass + + loadmods = LoadModules(config, logger=logger) + measuremods = MeasurementModules(config, logger=logger) + + # parse command line options + (cmd_opts, cmd_args) = parse_options(config, sys.argv[1:]) + + # copy the command line options into the rteval config section + # (cmd line overrides config file values) + config.AppendConfig('rteval', vars(cmd_opts)) + rtevcfg = config.GetSection('rteval') + + rtevcfg.workdir = os.path.abspath(cmd_opts.workdir) + + # Update log level, based on config/command line args + loglev = (not rtevcfg.quiet and (Log.ERR | Log.WARN)) \ + | (rtevcfg.verbose and Log.INFO) \ + | (rtevcfg.debugging and Log.DEBUG) + logger.SetLogVerbosity(loglev) + + logger.log(Log.DEBUG, "workdir: %s" % rtevcfg.workdir) + + # if --summarize was specified then just parse the XML, print it and exit + if cmd_opts.summarize or cmd_opts.rawhistogram: + if len(cmd_args) < 1: + raise RuntimeError, "Must specify at least one XML file with --summarize!" + + for x in cmd_args: + if cmd_opts.summarize: + summarize(x, '%s/rteval_text.xsl' % rtevcfg.installdir) + elif cmd_opts.rawhistogram: + summarize(x, '%s/rteval_histogram_raw.xsl' % rtevcfg.installdir) + + sys.exit(0) + + if os.getuid() != 0: + print "Must be root to run rteval!" + sys.exit(-1) + + logger.log(Log.DEBUG, '''rteval options: + workdir: %s + loaddir: %s + reportdir: %s + verbose: %s + debugging: %s + logging: %s + duration: %f + sysreport: %s''' % ( + rtevcfg.workdir, rtevcfg.srcdir, + rtevcfg.reportdir, rtevcfg.verbose, + rtevcfg.debugging, rtevcfg.logging, + rtevcfg.duration, rtevcfg.sysreport)) + + if not os.path.isdir(rtevcfg.workdir): + raise RuntimeError, "work directory %d does not exist" % rtevcfg.workdir + + + rteval = RtEval(config, loadmods, measuremods, logger) + rteval.Prepare(cmd_opts.onlyload) + + if cmd_opts.onlyload: + # If --onlyload were given, just kick off the loads and nothing more + # No reports will be created. + loadmods.Start() + nthreads = loadmods.Unleash() + logger.log(Log.INFO, "Started %i load threads - will run for %f seconds" % ( + nthreads, rtevcfg.duration)) + logger.log(Log.INFO, "No measurements will be performed, due to the --onlyload option") + time.sleep(rtevcfg.duration) + loadmods.Stop() + ec = 0 + else: + # ... otherwise, run the full measurement suite with loads + ec = rteval.Measure() + logger.log(Log.DEBUG, "exiting with exit code: %d" % ec) + + sys.exit(ec) + except KeyboardInterrupt: + sys.exit(0) diff --git a/rteval/__init__.py b/rteval/__init__.py index e1c8289..f3d93fe 100644 --- a/rteval/__init__.py +++ b/rteval/__init__.py @@ -1,7 +1,246 @@ """ -Copyright (c) 2008,2009,2010 Red Hat Inc. +Copyright (c) 2008-2012 Red Hat Inc. Realtime verification utility """ -__author__ = "Clark Williams <williams@redhat.com>" +__author__ = "Clark Williams <williams@redhat.com>, David Sommerseth <davids@redhat.com>" __license__ = "GPLv2 License" + +import os, signal, sys, threading, time +from datetime import datetime +from distutils import sysconfig +from sysinfo import SystemInfo +from modules.loads import LoadModules +from modules.measurement import MeasurementModules, MeasurementProfile +from rtevalReport import rtevalReport +from rtevalXMLRPC import rtevalXMLRPC +from Log import Log +import rtevalConfig, rtevalMailer + + + +sigint_received = False +def sigint_handler(signum, frame): + global sigint_received + sigint_received = True + print "*** SIGINT received - stopping rteval run ***" + + + +def sigterm_handler(signum, frame): + raise RuntimeError, "SIGTERM received!" + + + +class RtEval(rtevalReport): + def __init__(self, config, loadmods, measuremods, logger): + self.__version = "2.0_pre" + + if not isinstance(config, rtevalConfig.rtevalConfig): + raise TypeError("config variable is not an rtevalConfig object") + + if not isinstance(loadmods, LoadModules): + raise TypeError("loadmods variable is not a LoadModules object") + + if not isinstance(measuremods, MeasurementModules): + raise TypeError("measuremods variable is not a MeasurementModules object") + + if not isinstance(logger, Log): + raise TypeError("logger variable is not an Log object") + + self.__cfg = config + self.__logger = logger + self._loadmods = loadmods + self._measuremods = measuremods + + self.__rtevcfg = self.__cfg.GetSection('rteval') + self.__reportdir = None + + self._sysinfo = SystemInfo(self.__rtevcfg, logger=self.__logger) + + # prepare a mailer, if that's configured + if self.__cfg.HasSection('smtp'): + self.__mailer = rtevalMailer.rtevalMailer(self.__cfg.GetSection('smtp')) + else: + self.__mailer = None + + # Prepare XSLT processing, if enabled + if not self.__rtevcfg.xslt_report.startswith(self.__rtevcfg.installdir): + self.__rtevcfg.xslt_report = os.path.join(self.__rtevcfg.installdir, "rteval_text.xsl") + + if not os.path.exists(self.__rtevcfg.xslt_report): + raise RuntimeError("can't find XSL template (%s)!" % self.__rtevcfg.xslt_report) + + # Add rteval directory into module search path + sys.path.insert(0, '%s/rteval' % sysconfig.get_python_lib()) + + # Initialise the report module + rtevalReport.__init__(self, self.__version, + self.__rtevcfg.installdir, self.__rtevcfg.annotate) + + # If --xmlrpc-submit is given, check that we can access the server + if self.__rtevcfg.xmlrpc: + self.__xmlrpc = rtevalXMLRPC(self.__rtevcfg.xmlrpc, self.__logger, self.__mailer) + if not self.__xmlrpc.Ping(): + if not self.__rtevcfg.xmlrpc_noabort: + print "ERROR: Could not reach XML-RPC server '%s'. Aborting." % \ + self.__rtevcfg.xmlrpc + sys.exit(2) + else: + print "WARNING: Could not ping the XML-RPC server. Will continue anyway." + else: + self.__xmlrpc = None + + + def __show_remaining_time(self, remaining): + r = int(remaining) + days = r / 86400 + if days: r = r - (days * 86400) + hours = r / 3600 + if hours: r = r - (hours * 3600) + minutes = r / 60 + if minutes: r = r - (minutes * 60) + print "rteval time remaining: %d days, %d hours, %d minutes, %d seconds" % (days, hours, minutes, r) + + + def Prepare(self, onlyload = False): + builddir = os.path.join(self.__rtevcfg.workdir, 'rteval-build') + if not os.path.isdir(builddir): os.mkdir(builddir) + + # create our report directory + try: + # Only create a report dir if we're doing measurements + # or the loads logging is enabled + if not onlyload or self.__rtevcfg.logging: + self.__reportdir = self._make_report_dir(self.__rtevcfg.workdir, "summary.xml") + except Exception, e: + raise RuntimeError("Cannot create report directory (NFS with rootsquash on?) [%s]", str(e)) + + self.__logger.log(Log.INFO, "Preparing load modules") + params = {'workdir':self.__rtevcfg.workdir, + 'reportdir':self.__reportdir, + 'builddir':builddir, + 'srcdir':self.__rtevcfg.srcdir, + 'verbose': self.__rtevcfg.verbose, + 'debugging': self.__rtevcfg.debugging, + 'numcores':self._sysinfo.cpu_getCores(True), + 'logging':self.__rtevcfg.logging, + 'memsize':self._sysinfo.mem_get_size(), + 'numanodes':self._sysinfo.mem_get_numa_nodes(), + 'duration':self.__rtevcfg.duration, + } + self._loadmods.Setup(params) + + self.__logger.log(Log.INFO, "Preparing measurement modules") + self._measuremods.Setup(params) + + + def __RunMeasurementProfile(self, measure_profile): + if not isinstance(measure_profile, MeasurementProfile): + raise Exception("measure_profile is not an MeasurementProfile object") + + measure_start = None + (with_loads, run_parallel) = measure_profile.GetProfile() + self.__logger.log(Log.INFO, "Using measurement profile [loads: %s parallel: %s]" % ( + with_loads, run_parallel)) + try: + nthreads = 0 + + # start the loads + if with_loads: + self._loadmods.Start() + + print "rteval run on %s started at %s" % (os.uname()[2], time.asctime()) + print "started %d loads on %d cores" % (self._loadmods.ModulesLoaded(), self._sysinfo.cpu_getCores(True)), + if self._sysinfo.mem_get_numa_nodes() > 1: + print " with %d numa nodes" % self._sysinfo.mem_get_numa_nodes() + else: + print "" + print "Run duration: %d seconds" % self.__rtevcfg.duration + + # start the cyclictest thread + measure_profile.Start() + + # Uleash the loads and measurement threads + report_interval = int(self.__rtevcfg.report_interval) + nthreads = with_loads and self._loadmods.Unleash() or None + measure_profile.Unleash() + measure_start = datetime.now() + + # wait for time to expire or thread to die + signal.signal(signal.SIGINT, sigint_handler) + signal.signal(signal.SIGTERM, sigterm_handler) + self.__logger.log(Log.INFO, "waiting for duration (%f)" % self.__rtevcfg.duration) + stoptime = (time.time() + self.__rtevcfg.duration) + currtime = time.time() + rpttime = currtime + report_interval + load_avg_checked = 5 + while (currtime <= stoptime) and not sigint_received: + time.sleep(1.0) + if not measure_profile.isAlive(): + stoptime = currtime + self.__logger.log(Log.WARN, + "Measurement threads did not use the full time slot. Doing a controlled stop.") + + if with_loads: + if len(threading.enumerate()) < nthreads: + raise RuntimeError, "load thread died!" + + if not load_avg_checked: + self._loadmods.SaveLoadAvg() + load_avg_checked = 5 + else: + load_avg_checked -= 1 + + if currtime >= rpttime: + left_to_run = stoptime - currtime + self.__show_remaining_time(left_to_run) + rpttime = currtime + report_interval + print "load average: %.2f" % self._loadmods.GetLoadAvg() + currtime = time.time() + + self.__logger.log(Log.DEBUG, "out of measurement loop") + signal.signal(signal.SIGINT, signal.SIG_DFL) + signal.signal(signal.SIGTERM, signal.SIG_DFL) + + except RuntimeError, e: + print "Runtime error during measurement: %s", e + raise + + finally: + # stop measurement threads + measure_profile.Stop() + + # stop the loads + if with_loads: + self._loadmods.Stop() + + print "stopping run at %s" % time.asctime() + + # wait for measurement modules to finish calculating stats + measure_profile.WaitForCompletion() + + return measure_start + + + def Measure(self): + # Run the full measurement suite with reports + rtevalres = 0 + measure_start = None + for meas_prf in self._measuremods: + mstart = self.__RunMeasurementProfile(meas_prf) + if measure_start is None: + measure_start = mstart + + self._report(measure_start, self.__rtevcfg.xslt_report) + if self.__rtevcfg.sysreport: + self._sysinfo.run_sysreport(self.__reportdir) + + # if --xmlrpc-submit | -X was given, send our report to the given host + if self.__xmlrpc: + retvalres = self.__xmlrpc.SendReport(self._XMLreport()) + + self._sysinfo.copy_dmesg(self.__reportdir) + self._tar_results() + return rtevalres + diff --git a/rteval/modules/__init__.py b/rteval/modules/__init__.py index 6b656e8..1d927d2 100644 --- a/rteval/modules/__init__.py +++ b/rteval/modules/__init__.py @@ -22,8 +22,8 @@ # are deemed to be part of the source code. # -from Log import Log -from rtevalConfig import rtevalCfgSection +from rteval.Log import Log +from rteval.rtevalConfig import rtevalCfgSection import time, libxml2, threading __all__ = ["rtevalModulePrototype", "ModuleContainer", "RtEvalModules"] @@ -202,8 +202,8 @@ reference from the first import""" return self.__modsloaded[idxname] except KeyError: self.__logger.log(Log.INFO, "importing module %s" % modname) - mod = __import__("%s.%s" % (modroot, modname), - fromlist=modroot) + mod = __import__("rteval.%s.%s" % (modroot, modname), + fromlist="rteval.%s" % modroot) self.__modsloaded[idxname] = mod return mod diff --git a/rteval/modules/loads/__init__.py b/rteval/modules/loads/__init__.py index 66dad57..f4d4de4 100644 --- a/rteval/modules/loads/__init__.py +++ b/rteval/modules/loads/__init__.py @@ -27,9 +27,9 @@ import os import time import threading import libxml2 -from Log import Log -from rtevalConfig import rtevalCfgSection -from modules import RtEvalModules, rtevalModulePrototype +from rteval.Log import Log +from rteval.rtevalConfig import rtevalCfgSection +from rteval.modules import RtEvalModules, rtevalModulePrototype class LoadThread(rtevalModulePrototype): def __init__(self, name, config, logger=None): diff --git a/rteval/modules/loads/hackbench.py b/rteval/modules/loads/hackbench.py index cf09cfa..e040470 100644 --- a/rteval/modules/loads/hackbench.py +++ b/rteval/modules/loads/hackbench.py @@ -27,8 +27,8 @@ import sys, os, time, glob, subprocess, errno from signal import SIGTERM, SIGKILL -from modules.loads import CommandLineLoad -from Log import Log +from rteval.modules.loads import CommandLineLoad +from rteval.Log import Log class Hackbench(CommandLineLoad): diff --git a/rteval/modules/loads/kcompile.py b/rteval/modules/loads/kcompile.py index d0fb444..bcb033a 100644 --- a/rteval/modules/loads/kcompile.py +++ b/rteval/modules/loads/kcompile.py @@ -24,8 +24,8 @@ # import sys, os, glob, subprocess from signal import SIGTERM -from modules.loads import CommandLineLoad -from Log import Log +from rteval.modules.loads import CommandLineLoad +from rteval.Log import Log kernel_prefix="linux-2.6" diff --git a/rteval/modules/measurement/__init__.py b/rteval/modules/measurement/__init__.py index df96dfb..a12eba2 100644 --- a/rteval/modules/measurement/__init__.py +++ b/rteval/modules/measurement/__init__.py @@ -23,7 +23,7 @@ # import libxml2 -from modules import RtEvalModules, ModuleContainer +from rteval.modules import RtEvalModules, ModuleContainer class MeasurementProfile(RtEvalModules): diff --git a/rteval/modules/measurement/cyclictest.py b/rteval/modules/measurement/cyclictest.py index a04b52d..4be0238 100644 --- a/rteval/modules/measurement/cyclictest.py +++ b/rteval/modules/measurement/cyclictest.py @@ -26,8 +26,8 @@ # import os, sys, subprocess, signal, libxml2 -from Log import Log -from modules import rtevalModulePrototype +from rteval.Log import Log +from rteval.modules import rtevalModulePrototype class RunData(object): diff --git a/rteval/rteval.py b/rteval/rteval.py deleted file mode 100644 index 9dbd608..0000000 --- a/rteval/rteval.py +++ /dev/null @@ -1,483 +0,0 @@ -#!/usr/bin/python -tt -# -# rteval - script for evaluating platform suitability for RT Linux -# -# This program is used to determine the suitability of -# a system for use in a Real Time Linux environment. -# It starts up various system loads and measures event -# latency while the loads are running. A report is generated -# to show the latencies encountered during the run. -# -# Copyright 2009,2010,2011,2012 Clark Williams <williams@redhat.com> -# Copyright 2009,2010,2011,2012 David Sommerseth <davids@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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -# -# For the avoidance of doubt the "preferred form" of this code is one which -# is in an open unpatent encumbered format. Where cryptographic key signing -# forms part of the process of creating an executable the information -# including keys needed to generate an equivalently functional executable -# are deemed to be part of the source code. -# - -import sys -import os -import time -import threading -import optparse -import tempfile -import signal -from datetime import datetime -from distutils import sysconfig -from Log import Log -from sysinfo import SystemInfo -from modules.loads import LoadModules -from modules.measurement import MeasurementModules, MeasurementProfile -from rtevalReport import rtevalReport -from rtevalXMLRPC import rtevalXMLRPC - -# put local path at start of list to overide installed methods -sys.path.insert(0, "./rteval") -import rtevalConfig -import rtevalMailer - - -sigint_received = False -def sigint_handler(signum, frame): - global sigint_received - sigint_received = True - print "*** SIGINT received - stopping rteval run ***" - -def sigterm_handler(signum, frame): - raise RuntimeError, "SIGTERM received!" - -class RtEval(rtevalReport): - def __init__(self, cmdargs): - self.__version = "2.0_pre" - self.__workdir = os.getcwd() - self.__reportdir = None - self.__inifile = None - self.__cmd_opts = {} - - default_config = { - 'rteval': { - 'quiet' : False, - 'verbose' : False, - 'keepdata' : True, - 'debugging' : False, - 'duration' : '60', - 'sysreport' : False, - 'reportdir' : None, - 'reportfile' : None, - 'installdir' : '/usr/share/rteval', - 'srcdir' : '/usr/share/rteval/loadsource', - 'xmlrpc' : None, - 'xslt_report': '/usr/share/rteval/rteval_text.xsl', - 'report_interval': '600', - 'logging' : False, - }, - 'loads' : { - 'kcompile' : 'module', - 'hackbench' : 'module', - }, - 'kcompile' : { - 'source' : 'linux-2.6.21.tar.bz2', - 'jobspercore': '2', - }, - 'hackbench' : { - 'source' : 'hackbench.tar.bz2', - 'jobspercore': '5', - }, - 'cyclictest' : { - 'interval' : '100', - 'buckets' : '2000', - 'distance' : '25', - }, - 'measurement' : { - 'cyclictest' : 'module', - } - } - - # Prepare logging - self.__logger = Log() - self.__logger.SetLogVerbosity(Log.INFO) - - # setup initial configuration - self.__cfgobj = rtevalConfig.rtevalConfig(default_config, logger=self.__logger) - - # parse command line options - self.__cfg = self.__cfgobj.GetSection('rteval') - self.parse_options(cmdargs) - - # read in config file info - self.__inifile = self.__cfgobj.Load(self.__cmd_opts.inifile) - - # copy the command line options into the rteval config section - # (cmd line overrides config file values) - self.__cfgobj.AppendConfig('rteval', vars(self.__cmd_opts)) - self.__cfg = self.__cfgobj.GetSection('rteval') - - # Update log level, based on config/command line args - loglev = (not self.__cfg.quiet and (Log.ERR | Log.WARN)) \ - | (self.__cfg.verbose and Log.INFO) \ - | (self.__cfg.debugging and Log.DEBUG) - self.__logger.SetLogVerbosity(loglev) - - self.__logger.log(Log.DEBUG, "workdir: %s" % self.__workdir) - - # prepare a mailer, if that's configured - if self.__cfgobj.HasSection('smtp'): - self.__mailer = rtevalMailer.rtevalMailer(self.__cfgobj.GetSection('smtp')) - else: - self.__mailer = None - - self._sysinfo = SystemInfo(self.__cfg, logger=self.__logger) - self._loadmods = LoadModules(self.__cfgobj, logger=self.__logger) - self._measuremods = MeasurementModules(self.__cfgobj, logger=self.__logger) - - if not self.__cfg.xslt_report.startswith(self.__cfg.installdir): - self.__cfg.xslt_report = os.path.join(self.__cfg.installdir, "rteval_text.xsl") - - if not os.path.exists(self.__cfg.xslt_report): - raise RuntimeError, "can't find XSL template (%s)!" % self.__cfg.xslt_report - - # Add rteval directory into module search path - sys.path.insert(0, '%s/rteval' % sysconfig.get_python_lib()) - - # Initialise the report module - rtevalReport.__init__(self, self.__version, self.__cfg.installdir, self.__cmd_opts.annotate) - - # If --xmlrpc-submit is given, check that we can access the server - if self.__cfg.xmlrpc: - self.__xmlrpc = rtevalXMLRPC(self.__cfg.xmlrpc, self.__logger, self.__mailer) - if not self.__xmlrpc.Ping(): - if not self.__cmd_opts.xmlrpc_noabort: - print "ERROR: Could not reach XML-RPC server '%s'. Aborting." % self.__cfg.xmlrpc - sys.exit(2) - else: - print "WARNING: Could not ping the XML-RPC server. Will continue anyway." - else: - self.__xmlrpc = None - - - def parse_options(self, cmdargs): - '''parse the command line arguments''' - parser = optparse.OptionParser() - parser.add_option("-d", "--duration", dest="duration", - type="string", default=self.__cfg.duration, - help="specify length of test run (default: %default)") - parser.add_option("-v", "--verbose", dest="verbose", - action="store_true", default=self.__cfg.verbose, - help="turn on verbose prints (default: %default)") - parser.add_option("-w", "--workdir", dest="workdir", - type="string", default=self.__workdir, - help="top directory for rteval data (default: %default)") - parser.add_option("-l", "--loaddir", dest="srcdir", - type="string", default=self.__cfg.srcdir, - help="directory for load source tarballs (default: %default)") - parser.add_option("-i", "--installdir", dest="installdir", - type="string", default=self.__cfg.installdir, - help="place to locate installed templates (default: %default)") - parser.add_option("-s", "--sysreport", dest="sysreport", - action="store_true", default=self.__cfg.sysreport, - help='run sysreport to collect system data (default: %default)') - parser.add_option("-D", '--debug', dest='debugging', - action='store_true', default=self.__cfg.debugging, - help='turn on debug prints (default: %default)') - parser.add_option("-X", '--xmlrpc-submit', dest='xmlrpc', - action='store', default=self.__cfg.xmlrpc, metavar='HOST', - help='Hostname to XML-RPC server to submit reports') - parser.add_option("-P", "--xmlrpc-no-abort", dest="xmlrpc_noabort", - action='store_true', default=False, - help="Do not abort if XML-RPC server do not respond to ping request"); - parser.add_option("-Z", '--summarize', dest='summarize', - action='store_true', default=False, - help='summarize an already existing XML report') - parser.add_option("-H", '--raw-histogram', dest='rawhistogram', - action='store_true', default=False, - help='Generate raw histogram data for an already existing XML report') - parser.add_option("-f", "--inifile", dest="inifile", - type='string', default=None, - help="initialization file for configuring loads and behavior") - parser.add_option("-a", "--annotate", dest="annotate", - type="string", default=None, - help="Add a little annotation which is stored in the report") - parser.add_option("-L", "--logging", dest="logging", - action='store_true', default=False, - help='log the output of the loads in the report directory') - - parser.add_option("-O", "--onlyload", dest="onlyload", - action='store_true', default=False, - help="only run the loads (don't run measurement threads)") - - (self.__cmd_opts, self.__cmd_args) = parser.parse_args(args = cmdargs) - if self.__cmd_opts.duration: - mult = 1.0 - v = self.__cmd_opts.duration.lower() - if v.endswith('s'): - v = v[:-1] - elif v.endswith('m'): - v = v[:-1] - mult = 60.0 - elif v.endswith('h'): - v = v[:-1] - mult = 3600.0 - elif v.endswith('d'): - v = v[:-1] - mult = 3600.0 * 24.0 - self.__cmd_opts.duration = float(v) * mult - self.__workdir = os.path.abspath(self.__cmd_opts.workdir) - - - def show_remaining_time(self, remaining): - r = int(remaining) - days = r / 86400 - if days: r = r - (days * 86400) - hours = r / 3600 - if hours: r = r - (hours * 3600) - minutes = r / 60 - if minutes: r = r - (minutes * 60) - print "rteval time remaining: %d days, %d hours, %d minutes, %d seconds" % (days, hours, minutes, r) - - - def prepare(self, onlyload = False): - builddir = os.path.join(self.__workdir, 'rteval-build') - if not os.path.isdir(builddir): os.mkdir(builddir) - - # create our report directory - try: - # Only create a report dir if we're doing measurements - # or the loads logging is enabled - if not onlyload or self.__cfg.logging: - self.__reportdir = self._make_report_dir(self.__workdir, "summary.xml") - except Exception, e: - raise RuntimeError("Cannot create report directory (NFS with rootsquash on?) [%s]", str(e)) - - self.__logger.log(Log.INFO, "Preparing load modules") - params = {'workdir':self.__workdir, - 'reportdir':self.__reportdir, - 'builddir':builddir, - 'srcdir':self.__cfg.srcdir, - 'verbose': self.__cfg.verbose, - 'debugging': self.__cfg.debugging, - 'numcores':self._sysinfo.cpu_getCores(True), - 'logging':self.__cfg.logging, - 'memsize':self._sysinfo.mem_get_size(), - 'numanodes':self._sysinfo.mem_get_numa_nodes(), - 'duration':self.__cfg.duration, - } - self._loadmods.Setup(params) - - self.__logger.log(Log.INFO, "Preparing measurement modules") - self._measuremods.Setup(params) - - - def measure(self, measure_profile): - if not isinstance(measure_profile, MeasurementProfile): - raise Exception("measure_profile is not an MeasurementProfile object") - - measure_start = None - (with_loads, run_parallel) = measure_profile.GetProfile() - self.__logger.log(Log.INFO, "Using measurement profile [loads: %s parallel: %s]" % ( - with_loads, run_parallel)) - try: - nthreads = 0 - - # start the loads - if with_loads: - self._loadmods.Start() - - print "rteval run on %s started at %s" % (os.uname()[2], time.asctime()) - print "started %d loads on %d cores" % (self._loadmods.ModulesLoaded(), self._sysinfo.cpu_getCores(True)), - if self._sysinfo.mem_get_numa_nodes() > 1: - print " with %d numa nodes" % self._sysinfo.mem_get_numa_nodes() - else: - print "" - print "Run duration: %d seconds" % self.__cfg.duration - - # start the cyclictest thread - measure_profile.Start() - - - # Uleash the loads and measurement threads - report_interval = int(self.__cfg.report_interval) - nthreads = with_loads and self._loadmods.Unleash() or None - measure_profile.Unleash() - measure_start = datetime.now() - - # wait for time to expire or thread to die - signal.signal(signal.SIGINT, sigint_handler) - signal.signal(signal.SIGTERM, sigterm_handler) - self.__logger.log(Log.INFO, "waiting for duration (%f)" % self.__cfg.duration) - stoptime = (time.time() + self.__cfg.duration) - currtime = time.time() - rpttime = currtime + report_interval - load_avg_checked = 5 - while (currtime <= stoptime) and not sigint_received: - time.sleep(1.0) - if not measure_profile.isAlive(): - stoptime = currtime - self.__logger.log(Log.WARN, - "Measurement threads did not use the full time slot. Doing a controlled stop.") - - if with_loads: - if len(threading.enumerate()) < nthreads: - raise RuntimeError, "load thread died!" - - if not load_avg_checked: - self._loadmods.SaveLoadAvg() - load_avg_checked = 5 - else: - load_avg_checked -= 1 - - if currtime >= rpttime: - left_to_run = stoptime - currtime - self.show_remaining_time(left_to_run) - rpttime = currtime + report_interval - print "load average: %.2f" % self._loadmods.GetLoadAvg() - currtime = time.time() - - self.__logger.log(Log.DEBUG, "out of measurement loop") - signal.signal(signal.SIGINT, signal.SIG_DFL) - signal.signal(signal.SIGTERM, signal.SIG_DFL) - - except RuntimeError, e: - print "Runtime error during measurement: %s", e - raise - - finally: - # stop measurement threads - measure_profile.Stop() - - # stop the loads - if with_loads: - self._loadmods.Stop() - - print "stopping run at %s" % time.asctime() - - # wait for measurement modules to finish calculating stats - measure_profile.WaitForCompletion() - - return measure_start - - - def summarize(self, file): - isarchive = False - summary = file - if file.endswith(".tar.bz2"): - import tarfile - try: - t = tarfile.open(file) - except: - print "Don't know how to summarize %s (tarfile open failed)" % file - return - element = None - for f in t.getnames(): - if f.find('summary.xml') != -1: - element = f - break - if element == None: - print "No summary.xml found in tar archive %s" % file - return - tmp = tempfile.gettempdir() - self.__logger.log(Log.DEBUG, "extracting %s from %s for summarizing" % (element, file)) - t.extract(element, path=tmp) - summary = os.path.join(tmp, element) - isarchive = True - self._show_report(summary, 'rteval_text.xsl') - if isarchive: - os.unlink(summary) - - def rteval(self): - ''' main function for rteval''' - retval = 0; - - # if --summarize was specified then just parse the XML, print it and exit - if self.__cmd_opts.summarize or self.__cmd_opts.rawhistogram: - if len(self.cmd_arguments) < 1: - raise RuntimeError, "Must specify at least one XML file with --summarize!" - - for x in self.__cmd_args: - if self.__cmd_opts.summarize: - self.summarize(x) - elif self.__cmd_opts.rawhistogram: - self._show_report(x, 'rteval_histogram_raw.xsl') - - sys.exit(0) - - if os.getuid() != 0: - print "Must be root to run rteval!" - sys.exit(-1) - - self.__logger.log(Log.DEBUG, '''rteval options: - workdir: %s - loaddir: %s - reportdir: %s - verbose: %s - debugging: %s - logging: %s - duration: %f - sysreport: %s - inifile: %s''' % (self.__workdir, self.__cfg.srcdir, self.__reportdir, self.__cfg.verbose, - self.__cfg.debugging, self.__cfg.logging, self.__cfg.duration, - self.__cfg.sysreport, self.__inifile)) - - if not os.path.isdir(self.__workdir): - raise RuntimeError, "work directory %d does not exist" % self.__workdir - - self.prepare(self.__cmd_opts.onlyload) - - if self.__cmd_opts.onlyload: - # If --onlyload were given, just kick off the loads and nothing more - # No reports will be created. - self._loadmods.Start() - nthreads = self._loadmods.Unleash() - self.__logger.log(Log.INFO, "Started %i load threads - will run for %f seconds" % ( - nthreads, self.__cfg.duration)) - self.__logger.log(Log.INFO, "No measurements will be performed, due to the --onlyload option") - time.sleep(self.__cfg.duration) - self._loadmods.Stop() - retval = 0 - else: - # ... otherwise, run the full measurement suite with reports - measure_start = None - for meas_prf in self._measuremods: - mstart = self.measure(meas_prf) - if measure_start is None: - measure_start = mstart - self._report(measure_start, self.__cfg.xslt_report) - if self.__cfg.sysreport: - self._sysinfo.run_sysreport(self.__reportdir) - - # if --xmlrpc-submit | -X was given, send our report to this host - if self.__xmlrpc: - retval = self.__xmlrpc.SendReport(self._XMLreport()) - - self._sysinfo.copy_dmesg(self.__reportdir) - self._tar_results() - - self.__logger.log(Log.DEBUG, "exiting with exit code: %d" % retval) - - return retval - -if __name__ == '__main__': - import pwd, grp - - try: - rteval = RtEval(sys.argv[1:]) - ec = rteval.rteval() - sys.exit(ec) - except KeyboardInterrupt: - sys.exit(0) diff --git a/rteval/sysinfo/__init__.py b/rteval/sysinfo/__init__.py index 05a6042..0523c9f 100644 --- a/rteval/sysinfo/__init__.py +++ b/rteval/sysinfo/__init__.py @@ -25,7 +25,7 @@ # import sys, libxml2 -from Log import Log +from rteval.Log import Log from glob import glob from kernel import KernelInfo from services import SystemServices diff --git a/rteval/sysinfo/dmi.py b/rteval/sysinfo/dmi.py index d4079ad..2ab690f 100644 --- a/rteval/sysinfo/dmi.py +++ b/rteval/sysinfo/dmi.py @@ -27,7 +27,7 @@ import sys, os import libxml2, libxslt -from Log import Log +from rteval.Log import Log try: import dmidecode diff --git a/rteval/sysinfo/kernel.py b/rteval/sysinfo/kernel.py index aa482eb..0db69cc 100644 --- a/rteval/sysinfo/kernel.py +++ b/rteval/sysinfo/kernel.py @@ -27,8 +27,8 @@ # import sys, subprocess, os, libxml2 -from sysinfo.tools import getcmdpath -from Log import Log +from rteval.sysinfo.tools import getcmdpath +from rteval.Log import Log class KernelInfo(object): diff --git a/rteval/sysinfo/osinfo.py b/rteval/sysinfo/osinfo.py index 271ada2..7eb271a 100644 --- a/rteval/sysinfo/osinfo.py +++ b/rteval/sysinfo/osinfo.py @@ -27,7 +27,7 @@ import os, shutil, subprocess, libxml2 from glob import glob -from Log import Log +from rteval.Log import Log class OSInfo(object): def __init__(self, logger): diff --git a/rteval/sysinfo/services.py b/rteval/sysinfo/services.py index f38086e..de0dd7b 100644 --- a/rteval/sysinfo/services.py +++ b/rteval/sysinfo/services.py @@ -27,8 +27,8 @@ # import sys, subprocess, os, glob, fnmatch, libxml2 -from sysinfo.tools import getcmdpath -from Log import Log +from rteval.sysinfo.tools import getcmdpath +from rteval.Log import Log class SystemServices(object): |