aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClark Williams <williams@redhat.com>2017-03-16 09:02:12 -0500
committerClark Williams <williams@redhat.com>2017-03-16 09:02:12 -0500
commitb2ae9af1c5911865b7e06d58769e2f2a0415a396 (patch)
treef7e0f13ca759469a6e463c313e4a64aef0fb5960
parent7dbb3b296bbd136d635f8828d8375c09adf199de (diff)
parentad867c9609957fd5edf05e4e1530f508300a2720 (diff)
downloadrteval-b2ae9af1c5911865b7e06d58769e2f2a0415a396.tar.gz
Merge branch 'v2/kcompile-by-nodes' into v2/masterv2.14
-rw-r--r--Makefile6
-rw-r--r--rteval-loads.spec9
-rw-r--r--rteval.spec12
-rw-r--r--rteval/modules/loads/kcompile.py161
-rw-r--r--rteval/modules/measurement/cyclictest.py2
-rw-r--r--rteval/rtevalConfig.py6
-rw-r--r--rteval/sysinfo/__init__.py1
-rw-r--r--rteval/sysinfo/dmi.py5
-rw-r--r--rteval/systopology.py246
-rw-r--r--rteval/version.py2
10 files changed, 378 insertions, 72 deletions
diff --git a/Makefile b/Makefile
index bd8e95f..4392d5f 100644
--- a/Makefile
+++ b/Makefile
@@ -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'