aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClark Williams <williams@redhat.com>2016-11-22 21:28:38 -0600
committerClark Williams <williams@redhat.com>2016-11-22 21:28:38 -0600
commit433768759bc852f2641d027c8315aaa6772084ad (patch)
tree66f14ad653adc93c38ab917194df48e6505dd3d6
parentfdd69c434f2654702e5c3cac26fc1476ad5c18fd (diff)
downloadrteval-433768759bc852f2641d027c8315aaa6772084ad.tar.gz
add systopology.py
This file adds classes for Cpus, NumaNodes and SysTopology. The SysTopology class is intended to be instantiated early in the rteval run and provide information to modules that will allow them to intelligently place loads and measurement programs based on the system resources. Signed-off-by: Clark Williams <williams@redhat.com>
-rw-r--r--rteval/sysinfo/systopology.py239
1 files changed, 239 insertions, 0 deletions
diff --git a/rteval/sysinfo/systopology.py b/rteval/sysinfo/systopology.py
new file mode 100644
index 0000000..a988907
--- /dev/null
+++ b/rteval/sysinfo/systopology.py
@@ -0,0 +1,239 @@
+# -*- 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 Cpus(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
+
+ # find the nodeid of a particular cpu
+ def getnodeid(self, n):
+ cpupath = os.path.join(Cpus.cpupath,'cpu%d' % n)
+ return int(self.__sysread(cpupath, "topology/physical_package_id"))
+
+ # check whether cpu n is online
+ def isonline(self, n):
+ if n == 0:
+ return True
+ path = os.path.join(Cpus.cpupath,'cpu%d' % n)
+ if os.path.exists(path):
+ return self.__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"
+
+ def __init__(self, path):
+ self.path = path
+ self.nodeid = int(os.path.basename(path)[4:].strip())
+ self.cpus = Cpus(self.__sysread(self.path, "cpulist"))
+ self.getmeminfo()
+
+ def __contains__(self, cpu):
+ return cpu in self.cpus
+
+ def __len__(self):
+ return len(self.cpus)
+
+ def __str__(self):
+ return self.getcpustr()
+
+ def __sysread(self, path, element):
+ fp = open(os.path.join(path,element), "r")
+ return fp.readline().strip()
+
+ # 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 __contains__(self, node):
+ for n in self.nodes:
+ if self.nodes[n].nodeid == node:
+ return True
+ return False
+
+ def __getitem__(self, key):
+ return self.nodes[key]
+
+ def __iter__(self):
+ self.current = 0
+ return self
+
+ def next(self):
+ if self.current >= len(self.nodes):
+ raise StopIteration
+ n = self.nodes[self.current]
+ self.current += 1
+ return n
+
+ def __sysread(self, path, obj):
+ fp = open(os.path.join(path, obj), "r")
+ return fp.readline().strip()
+
+ 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 "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()