diff options
author | Clark Williams <williams@redhat.com> | 2017-03-16 09:02:12 -0500 |
---|---|---|
committer | Clark Williams <williams@redhat.com> | 2017-03-16 09:02:12 -0500 |
commit | b2ae9af1c5911865b7e06d58769e2f2a0415a396 (patch) | |
tree | f7e0f13ca759469a6e463c313e4a64aef0fb5960 | |
parent | 7dbb3b296bbd136d635f8828d8375c09adf199de (diff) | |
parent | ad867c9609957fd5edf05e4e1530f508300a2720 (diff) | |
download | rteval-b2ae9af1c5911865b7e06d58769e2f2a0415a396.tar.gz |
Merge branch 'v2/kcompile-by-nodes' into v2/masterv2.14
-rw-r--r-- | Makefile | 6 | ||||
-rw-r--r-- | rteval-loads.spec | 9 | ||||
-rw-r--r-- | rteval.spec | 12 | ||||
-rw-r--r-- | rteval/modules/loads/kcompile.py | 161 | ||||
-rw-r--r-- | rteval/modules/measurement/cyclictest.py | 2 | ||||
-rw-r--r-- | rteval/rtevalConfig.py | 6 | ||||
-rw-r--r-- | rteval/sysinfo/__init__.py | 1 | ||||
-rw-r--r-- | rteval/sysinfo/dmi.py | 5 | ||||
-rw-r--r-- | rteval/systopology.py | 246 | ||||
-rw-r--r-- | rteval/version.py | 2 |
10 files changed, 378 insertions, 72 deletions
@@ -15,13 +15,13 @@ MANDIR := $(DESTDIR)/$(PREFIX)/share/man PYLIB := $(DESTDIR)$(shell python -c 'import distutils.sysconfig; print distutils.sysconfig.get_python_lib()') LOADDIR := loadsource -KLOAD := $(LOADDIR)/linux-2.6.39.tar.bz2 +KLOAD := $(LOADDIR)/linux-4.9.tar.xz BLOAD := $(LOADDIR)/dbench-4.0.tar.gz LOADS := $(KLOAD) $(BLOAD) runit: [ -d $(HERE)/run ] || mkdir run - python rteval-cmd -D -L -v --workdir=$(HERE)/run --loaddir=$(HERE)/loadsource --duration=$(D) -f $(HERE)/rteval.conf -i $(HERE)/rteval + python rteval-cmd -D -L -v --workdir=$(HERE)/run --loaddir=$(HERE)/loadsource --duration=$(D) -f $(HERE)/rteval.conf -i $(HERE)/rteval $(EXTRA) load: [ -d ./run ] || mkdir run @@ -85,7 +85,7 @@ rpm_prep: rm -rf rpm mkdir -p rpm/{BUILD,RPMS,SRPMS,SOURCES,SPECS} -rpms rpm: rpm_prep rtevalrpm loadrpm xmlrpcrpm +rpms rpm: rpm_prep rtevalrpm loadrpm rtevalrpm: rteval-$(VERSION).tar.bz2 cp $^ rpm/SOURCES diff --git a/rteval-loads.spec b/rteval-loads.spec index 02956a5..96e4a59 100644 --- a/rteval-loads.spec +++ b/rteval-loads.spec @@ -1,11 +1,11 @@ Name: rteval-loads -Version: 1.3 -Release: 3%{?dist} +Version: 1.4 +Release: 1%{?dist} Summary: Source files for rteval loads Group: Development/Tools License: GPLv2 URL: http://git.kernel.org/?p=linux/kernel/git/clrkwllms/rteval.git -Source0: http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.39.tar.bz2 +Source0: http://www.kernel.org/pub/linux/kernel/v4.9/linux-4.9.tar.xz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Requires: gcc binutils make kernel-headers @@ -37,6 +37,9 @@ rm -rf ${RPM_BUILD_ROOT} %doc %changelog +* Tue Jan 10 2017 Clark Williams <williams@redhat.com> - 1.4-1 +- updated kernel tarball to 4.9 [1432625] + * Fri Jun 5 2015 Clark Williams <williams@redhat.com> - 1.3-3 - add requires for kernel-header package [1228740] diff --git a/rteval.spec b/rteval.spec index 4a76c5e..a41a342 100644 --- a/rteval.spec +++ b/rteval.spec @@ -2,7 +2,7 @@ %{!?python_ver: %define python_ver %(%{__python} -c "import sys ; print sys.version[:3]")} Name: rteval -Version: 2.12 +Version: 2.14 Release: 1%{?dist} Summary: Utility to evaluate system suitability for RT Linux @@ -17,7 +17,7 @@ Requires: python Requires: python-schedutils python-ethtool python-lxml Requires: python-dmidecode >= 3.10 Requires: rt-tests >= 0.97 -Requires: rteval-loads >= 1.2 +Requires: rteval-loads >= 1.4 Requires: rteval-common => %{version}-%{release} Requires: trace-cmd Requires: sysstat @@ -74,7 +74,7 @@ rm -rf $RPM_BUILD_ROOT %{python_sitelib}/rteval/version.py* %{python_sitelib}/rteval/Log.py* %{python_sitelib}/rteval/misc.py* - +%{python_sitelib}/rteval/systopology.py* %files %defattr(-,root,root,-) @@ -95,6 +95,12 @@ rm -rf $RPM_BUILD_ROOT /usr/bin/rteval %changelog +* Thu Mar 16 2017 Clark Williams <williams@redhat.com> - 2.14-1 +- removed leftover import of systopology from sysinfo + +* Wed Mar 15 2017 Clark Williams <williams@redhat.com> - 2.13-2 +- Updated specfile to correct version and bz [1382155] + * Tue Sep 20 2016 Clark Williams <williams@rehdat.com> - 2.12-1 - handle empty environment variables SUDO_USER and USER [1312057] diff --git a/rteval/modules/loads/kcompile.py b/rteval/modules/loads/kcompile.py index 1eb2cde..ef636c2 100644 --- a/rteval/modules/loads/kcompile.py +++ b/rteval/modules/loads/kcompile.py @@ -1,6 +1,7 @@ # # Copyright 2009 - 2013 Clark Williams <williams@redhat.com> # Copyright 2012 - 2013 David Sommerseth <davids@redhat.com> +# Copyright 2014 - 2017 Clark Williams <williams@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 @@ -23,19 +24,89 @@ # are deemed to be part of the source code. # -import sys, os, glob, subprocess +import sys, os, os.path, glob, subprocess from signal import SIGTERM from rteval.modules import rtevalRuntimeError from rteval.modules.loads import CommandLineLoad from rteval.Log import Log from rteval.misc import expand_cpulist +from rteval.systopology import SysTopology + +kernel_prefix="linux-4.9" + +class KBuildJob(object): + '''Class to manage a build job bound to a particular node''' + + def __init__(self, node, kdir, logger=None): + self.kdir = kdir + self.jobid = None + self.node = node + self.logger = logger + self.builddir = os.path.dirname(kdir) + self.objdir = "%s/node%d" % (self.builddir, int(node)) + if not os.path.isdir(self.objdir): + os.mkdir(self.objdir) + if os.path.exists('/usr/bin/numactl'): + self.binder = 'numactl --cpunodebind %d' % int(self.node) + else: + self.binder = 'taskset -c %s' % str(self.node) + self.jobs = self.calc_jobs_per_cpu() * len(self.node) + self.log(Log.DEBUG, "node %d: jobs == %d" % (int(node), self.jobs)) + self.runcmd = "%s make O=%s -C %s -j%d bzImage modules" % (self.binder, self.objdir, self.kdir, self.jobs) + self.cleancmd = "%s make O=%s -C %s clean allmodconfig" % (self.binder, self.objdir, self.kdir) + self.log(Log.DEBUG, "node%d kcompile command: %s" % (int(node), self.runcmd)) + + def __str__(self): + return self.runcmd + + def log(self, logtype, msg): + if self.logger: + self.logger.log(logtype, "[kcompile node%d] %s" % (int(self.node), msg)) + + def calc_jobs_per_cpu(self): + mult = 2 + self.log(Log.DEBUG, "calulating jobs for node %d" % int(self.node)) + # get memory total in gigabytes + mem = int(self.node.meminfo['MemTotal']) / 1024.0 / 1024.0 / 1024.0 + # ratio of gigabytes to #cores + ratio = float(mem) / float(len(self.node)) + if ratio < 1.0: + ratio = 1.0 + if ratio < 1.0 or ratio > 2.0: + mult = 1 + self.log(Log.DEBUG, "memory/cores ratio on node %d: %f" % (int(self.node), ratio)) + self.log(Log.DEBUG, "returning jobs/core value of: %d" % int(ratio) * mult) + return int(int(ratio) * int(mult)) + + def clean(self, sin=None, sout=None, serr=None): + self.log(Log.DEBUG, "cleaning objdir %s" % self.objdir) + subprocess.call(self.cleancmd, shell=True, + stdin=sin, stdout=sout, stderr=serr) + + def run(self, sin=None, sout=None, serr=None): + self.log(Log.INFO, "starting workload on node %d" % int(self.node)) + self.log(Log.DEBUG, "running on node %d: %s" % (int(self.node), self.runcmd)) + self.jobid = subprocess.Popen(self.runcmd, shell=True, + stdin=sin, stdout=sout, stderr=serr) + + def isrunning(self): + if self.jobid == None: + return False + return (self.jobid.poll() == None) + + def stop(self): + if not self.jobid: + return True + return self.jobid.terminate() -kernel_prefix="linux-2.6" class Kcompile(CommandLineLoad): def __init__(self, config, logger): + self.buildjobs = {} + self.config = config + self.topology = SysTopology() CommandLineLoad.__init__(self, "kcompile", config, logger) - + self.logger = logger def _WorkloadSetup(self): # find our source tarball @@ -80,13 +151,18 @@ class Kcompile(CommandLineLoad): break if kdir == None: raise rtevalRuntimeError(self, "Can't find kernel directory!") - self.jobs = 1 # We only run one instance of the kcompile job self.mydir = os.path.join(self.builddir, kdir) self._log(Log.DEBUG, "mydir = %s" % self.mydir) + self._log(Log.DEBUG, "systopology: %s" % self.topology) + self.jobs = len(self.topology) + self.args = [] + for n in self.topology: + self._log(Log.DEBUG, "Configuring build job for node %d" % int(n)) + self.buildjobs[n] = KBuildJob(n, self.mydir, self.logger) + self.args.append(str(self.buildjobs[n])+";") def _WorkloadBuild(self): - self._log(Log.DEBUG, "setting up all module config file in %s" % self.mydir) null = os.open("/dev/null", os.O_RDWR) if self._logging: out = self.open_logfile("kcompile-build.stdout") @@ -94,9 +170,9 @@ class Kcompile(CommandLineLoad): else: out = err = null - # clean up from potential previous run + # clean up any damage from previous runs try: - ret = subprocess.call(["make", "-C", self.mydir, "mrproper", "allmodconfig"], + ret = subprocess.call(["make", "-C", self.mydir, "mrproper"], stdin=null, stdout=out, stderr=err) if ret: raise rtevalRuntimeError(self, "kcompile setup failed: %d" % ret) @@ -104,31 +180,15 @@ class Kcompile(CommandLineLoad): self._log(Log.DEBUG, "keyboard interrupt, aborting") return self._log(Log.DEBUG, "ready to run") - os.close(null) if self._logging: os.close(out) os.close(err) + # clean up object dirs and make sure each has a config file + for n in self.topology: + self.buildjobs[n].clean(sin=null,sout=null,serr=null) + os.close(null) self._setReady() - - def __calc_numjobs(self): - mult = int(self._cfg.setdefault('jobspercore', 1)) - mem = self.memsize[0] - if self.memsize[1] == 'KB': - mem = mem / (1024.0 * 1024.0) - elif self.memsize[1] == 'MB': - mem = mem / 1024.0 - elif self.memsize[1] == 'TB': - mem = mem * 1024 - ratio = float(mem) / float(self.num_cpus) - if ratio > 1.0: - njobs = self.num_cpus * mult - else: - self._log(Log.DEBUG, "Low memory system (%f GB/core)! Dropping jobs to one per core" % ratio) - njobs = self.num_cpus - return njobs - - def _WorkloadPrepare(self): self.__nullfd = os.open("/dev/null", os.O_RDWR) if self._logging: @@ -143,41 +203,30 @@ class Kcompile(CommandLineLoad): else: cpulist = "" - self.jobs = self.__calc_numjobs() - self._log(Log.DEBUG, "starting loop (jobs: %d)" % self.jobs) - - self.args = ["make", "-C", self.mydir, - "-j%d" % self.jobs ] - - if cpulist: - self.args = ["taskset", '-c', cpulist] + self.args - - self.__kcompileproc = None - - def _WorkloadTask(self): - if not self.__kcompileproc or self.__kcompileproc.poll() is not None: - # If kcompile has not been kicked off yet, or have completed, - # restart it - self._log(Log.DEBUG, "Kicking off kcompile: %s" % " ".join(self.args)) - self.__kcompileproc = subprocess.Popen(self.args, - stdin=self.__nullfd, - stdout=self.__outfd, - stderr=self.__errfd) - + for n in self.topology: + if not self.buildjobs[n]: + raise RuntimeError, "Build job not set up for node %d" % int(n) + if self.buildjobs[n].jobid is None or self.buildjobs[n].jobid.poll() is not None: + self._log(Log.INFO, "Starting load on node %d" % n) + self.buildjobs[n].run(self.__nullfd, self.__outfd, self.__errfd) def WorkloadAlive(self): - # Let _WorkloadTask() kick off new runs, if it stops - thus - # kcompile will always be alive + # if any of the jobs has stopped, return False + for n in self.topology: + if self.buildjobs[n].jobid.poll() is not None: + return False return True def _WorkloadCleanup(self): self._log(Log.DEBUG, "out of stopevent loop") - if self.__kcompileproc.poll() == None: - self._log(Log.DEBUG, "killing compile job with SIGTERM") - os.kill(self.__kcompileproc.pid, SIGTERM) - self.__kcompileproc.wait() + for n in self.buildjobs: + if self.buildjobs[n].jobid.poll() == None: + self._log(Log.DEBUG, "stopping job on node %d" % int(n)) + self.buildjobs[n].jobid.terminate() + self.buildjobs[n].jobid.wait() + del self.buildjobs[n].jobid os.close(self.__nullfd) del self.__nullfd if self._logging: @@ -185,14 +234,12 @@ class Kcompile(CommandLineLoad): del self.__outfd os.close(self.__errfd) del self.__errfd - del self.__kcompileproc self._setFinished() - def ModuleParameters(): return {"source": {"descr": "Source tar ball", - "default": "linux-2.6.21.tar.bz2", + "default": "linux-4.9.tar.xz", "metavar": "TARBALL"}, "jobspercore": {"descr": "Number of working threads per core", "default": 2, diff --git a/rteval/modules/measurement/cyclictest.py b/rteval/modules/measurement/cyclictest.py index b871695..c5b3055 100644 --- a/rteval/modules/measurement/cyclictest.py +++ b/rteval/modules/measurement/cyclictest.py @@ -259,7 +259,7 @@ class Cyclictest(rtevalModulePrototype): self.__cmd = ['cyclictest', self.__interval, - '-qmu', + '-qmun', '-h %d' % self.__buckets, "-p%d" % int(self.__priority), ] diff --git a/rteval/rtevalConfig.py b/rteval/rtevalConfig.py index 5a4aa5f..16ac2d0 100644 --- a/rteval/rtevalConfig.py +++ b/rteval/rtevalConfig.py @@ -33,7 +33,7 @@ import os, sys import ConfigParser from Log import Log - +from systopology import SysTopology def get_user_name(): name = os.getenv('SUDO_USER') @@ -188,6 +188,10 @@ class rtevalConfig(object): self.__config_files = [] self.__logger = logger + # get our system topology info + self.__systopology = SysTopology() + print("got system topology: %s" % self.__systopology) + # Import the default config first for sect, vals in default_config.items(): self.__update_section(sect, vals) diff --git a/rteval/sysinfo/__init__.py b/rteval/sysinfo/__init__.py index fe5fbd6..0de985b 100644 --- a/rteval/sysinfo/__init__.py +++ b/rteval/sysinfo/__init__.py @@ -34,7 +34,6 @@ from osinfo import OSInfo from network import NetworkInfo import dmi - class SystemInfo(KernelInfo, SystemServices, dmi.DMIinfo, CPUtopology, MemoryInfo, OSInfo, NetworkInfo): def __init__(self, config, logger=None): self.__logger = logger diff --git a/rteval/sysinfo/dmi.py b/rteval/sysinfo/dmi.py index 84ca97f..f8e2500 100644 --- a/rteval/sysinfo/dmi.py +++ b/rteval/sysinfo/dmi.py @@ -27,8 +27,9 @@ import sys, os import libxml2, lxml.etree -from rteval import rtevalConfig, xmlout from rteval.Log import Log +from rteval import xmlout +from rteval import rtevalConfig try: import dmidecode @@ -38,7 +39,7 @@ except: pass def ProcessWarnings(): - + if not hasattr(dmidecode, 'get_warnings'): return diff --git a/rteval/systopology.py b/rteval/systopology.py new file mode 100644 index 0000000..7c0985e --- /dev/null +++ b/rteval/systopology.py @@ -0,0 +1,246 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2016 - Clark Williams <williams@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., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 os, sys +import os.path +import glob + +def _sysread(path, obj): + fp = open(os.path.join(path,obj), "r") + return fp.readline().strip() + +# +# class to provide access to a list of cpus +# + +class CpuList(object): + "Object that represents a group of system cpus" + + cpupath = '/sys/devices/system/cpu' + + def __init__(self, cpulist): + if type(cpulist) is list: + self.cpulist = cpulist + elif type(cpulist) is str: + self.cpulist = self.__expand_cpulist(cpulist) + self.cpulist.sort() + + def __str__(self): + return self.__collapse_cpulist(self.cpulist) + + def __contains__(self, cpu): + return cpu in self.cpulist + + def __len__(self): + return len(self.cpulist) + + + # return the index of the last element of a sequence + # that steps by one + def __longest_sequence(self, cpulist): + lim = len(cpulist) + for idx,val in enumerate(cpulist): + if idx+1 == lim: + break + if int(cpulist[idx+1]) != (int(cpulist[idx])+1): + return idx + return lim - 1 + + + # + # collapse a list of cpu numbers into a string range + # of cpus (e.g. 0-5, 7, 9) + # + def __collapse_cpulist(self, cpulist): + if len(cpulist) == 0: + return "" + idx = self.__longest_sequence(cpulist) + if idx == 0: + seq = str(cpulist[0]) + else: + if idx == 1: + seq = "%d,%d" % (cpulist[0], cpulist[idx]) + else: + seq = "%d-%d" % (cpulist[0], cpulist[idx]) + + rest = self.__collapse_cpulist(cpulist[idx+1:]) + if rest == "": + return seq + return ",".join((seq, rest)) + + # expand a string range into a list + # don't error check against online cpus + def __expand_cpulist(self, cpulist): + '''expand a range string into an array of cpu numbers''' + result = [] + for part in cpulist.split(','): + if '-' in part: + a, b = part.split('-') + a, b = int(a), int(b) + result.extend(range(a, b + 1)) + else: + a = int(part) + result.append(a) + return [ int(i) for i in list(set(result)) ] + + # returns the list of cpus tracked + def getcpulist(self): + return self.cpulist + + # check whether cpu n is online + def isonline(self, n): + if n not in self.cpulist: + raise RuntimeError, "invalid cpu number %d" % n + if n == 0: + return True + path = os.path.join(CpuList.cpupath,'cpu%d' % n) + if os.path.exists(path): + return _sysread(path, "online") == 1 + return False + +# +# class to abstract access to NUMA nodes in /sys filesystem +# + +class NumaNode(object): + "class representing a system NUMA node" + + # constructor argument is the full path to the /sys node file + # e.g. /sys/devices/system/node/node0 + def __init__(self, path): + self.path = path + self.nodeid = int(os.path.basename(path)[4:].strip()) + self.cpus = CpuList(_sysread(self.path, "cpulist")) + self.getmeminfo() + + # function for the 'in' operator + def __contains__(self, cpu): + return cpu in self.cpus + + # allow the 'len' builtin + def __len__(self): + return len(self.cpus) + + # string representation of the cpus for this node + def __str__(self): + return self.getcpustr() + + def __int__(self): + return self.nodeid + + # read info about memory attached to this node + def getmeminfo(self): + self.meminfo = {} + for l in open(os.path.join(self.path, "meminfo"), "r"): + elements = l.split() + key=elements[2][0:-1] + val=int(elements[3]) + if len(elements) == 5 and elements[4] == "kB": + val *= 1024 + self.meminfo[key] = val + + # return list of cpus for this node as a string + def getcpustr(self): + return str(self.cpus) + + # return list of cpus for this node + def getcpulist(self): + return self.cpus.getcpulist() + +# +# Class to abstract the system topology of numa nodes and cpus +# +class SysTopology(object): + "Object that represents the system's NUMA-node/cpu topology" + + cpupath = '/sys/devices/system/cpu' + nodepath = '/sys/devices/system/node' + + def __init__(self): + self.nodes = {} + self.getinfo() + + def __len__(self): + return len(self.nodes.keys()) + + def __str__(self): + s = "%d node system" % len(self.nodes.keys()) + s += " (%d cores per node)" % (len(self.nodes[self.nodes.keys()[0]])) + return s + + # inplement the 'in' function + def __contains__(self, node): + for n in self.nodes: + if self.nodes[n].nodeid == node: + return True + return False + + # allow indexing for the nodes + def __getitem__(self, key): + return self.nodes[key] + + # allow iteration over the cpus for the node + def __iter__(self): + self.current = 0 + return self + + # iterator function + def next(self): + if self.current >= len(self.nodes): + raise StopIteration + n = self.nodes[self.current] + self.current += 1 + return n + + def getinfo(self): + nodes = glob.glob(os.path.join(SysTopology.nodepath, 'node[0-9]*')) + if not nodes: + raise RuntimeError, "No valid nodes found in %s!" % SysTopology.nodepath + nodes.sort() + for n in nodes: + node = int(os.path.basename(n)[4:]) + self.nodes[node] = NumaNode(n) + + def getnodes(self): + return self.nodes.keys() + + def getcpus(self, node): + return self.nodes[node] + + + +if __name__ == "__main__": + + def unit_test(): + s = SysTopology() + print s + print "number of nodes: %d" % len(s) + for n in s: + print "node[%d]: %s" % (n.nodeid, n) + print "system has numa node 0: %s" % (0 in s) + print "system has numa node 2: %s" % (2 in s) + print "system has numa node 24: %s" % (24 in s) + + unit_test() diff --git a/rteval/version.py b/rteval/version.py index b67143a..fdc6b43 100644 --- a/rteval/version.py +++ b/rteval/version.py @@ -23,4 +23,4 @@ # are deemed to be part of the source code. # -RTEVAL_VERSION = '2.12' +RTEVAL_VERSION = '2.14' |