blob: 7c0985e9d26e93fada19e80aaac4c458fb734987 [file] [log] [blame]
# -*- 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()