You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cpuset/cpuset/cset.py

433 lines
15 KiB
Python

"""Cpuset class and cpuset graph, importing module will create model
"""
__copyright__ = """
Copyright (C) 2008 Novell Inc.
Author: Alex Tsariounov <alext@novell.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
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
"""
import os, re, sys, logging
if __name__ == '__main__':
sys.path.insert(0, "..")
logging.basicConfig()
from cpuset.util import *
global log
log = logging.getLogger('cset')
class CpuSet(object):
# sets is a class variable dict that keeps track of all
# cpusets discovered such that we can link them in properly.
# The basepath is it's base path, the sets are indexed via
# a relative path from this basepath.
sets = {}
basepath = ''
def __init__(self, path=None):
log.debug("initializing CpuSet")
if (path == None):
# recursively find all cpusets and link together
# note: a breadth-first search could do this in one
# pass, but there are never many cpusets, so
# that optimization is left for the future
log.debug("finding all cpusets")
path = self.locate_cpusets()
CpuSet.basepath = path
log.debug("creating root node at %s", path)
self.__root = True
self.name = 'root'
self.path = '/'
self.parent = self
if (CpuSet.sets):
del CpuSet.sets
CpuSet.sets = {}
CpuSet.sets[self.path] = self
# bottom-up search otherwise links will not exist
log.debug("starting bottom-up discovery walk...")
for dir, dirs, files in os.walk(path, topdown=False):
log.debug("*** walking %s", dir)
if dir != CpuSet.basepath:
node = CpuSet(dir)
else:
node = self
node.subsets = []
for sub in dirs:
if len(sub) > 0:
relpath = os.path.join(dir,sub).replace(CpuSet.basepath, '')
else:
relpath = '/'
node.subsets.append(CpuSet.sets[relpath])
log.debug("%s has %i subsets: [%s]", dir,
len(node.subsets), '|'.join(dirs))
log.debug("staring top-down parenting walk...")
for dir, dirs, files in os.walk(path):
dir = dir.replace(CpuSet.basepath, '')
if len(dir) == 0: dir = '/'
node = CpuSet.sets[dir]
log.debug("~~~ walking %s", node.path)
if dir == '/':
log.debug("parent is self (root cpuset), skipping")
else:
parpath = dir[0:dir.rfind('/')]
log.debug('parpath decodes to: %s from dir of: %s', parpath, dir)
if CpuSet.sets.has_key(parpath):
log.debug("parent is %s", parpath)
node.parent = CpuSet.sets[parpath]
else:
log.debug("parent is root cpuset")
node.parent = CpuSet.sets['/']
log.debug("found %i cpusets", len(CpuSet.sets))
else:
# one new cpuset node
log.debug("new cpuset node absolute: %s", path)
if len(path) > len(CpuSet.basepath):
path = path.replace(CpuSet.basepath, '')
else:
path = '/'
log.debug(" relative: %s", path)
if CpuSet.sets.has_key(path):
log.debug("the cpuset %s already exists, skipping", path)
self = CpuSet.sets[path] # questionable....
return
cpus = CpuSet.basepath + path + "/cpus"
if not os.access(cpus, os.F_OK):
# not a cpuset directory
str = '%s is not a cpuset directory' % (CpuSet.basepath + path)
log.error(str)
raise CpusetException(str)
self.__root = False
self.read_cpuset(path)
CpuSet.sets[path] = self
def locate_cpusets(self):
log.debug("locating cpuset filesystem...")
cpuset = re.compile(r"none (/.+) cpuset .+")
cgroup = re.compile(r"none (/.+) cgroup .+")
path = None
f = file("/proc/mounts")
for line in f:
res = cpuset.search(line)
if res:
path = res.group(1)
break
else:
if cgroup.search(line):
groups = line.split()
if re.search("cpuset", groups[3]):
path = groups[1]
break
f.close()
if not path:
# mounted cpusets not found, so mount them
# FIXME: provide default mount directory from config file
if not os.access("/cpusets", os.F_OK):
os.mkdir("/cpusets")
ret = os.system("mount -t cpuset none /cpusets")
if ret:
raise CpusetException(
'mount of cpuset filesystem failed, do you have permission?')
path = "/cpusets"
log.debug("cpusets mounted at: " + path)
return path
def read_cpuset(self, path):
log.debug("reading cpuset passed relpath: %s", path)
self.path = path
log.debug("...path=%s", path)
self.name = path[path.rfind('/')+1:]
log.debug("...name=%s", self.name)
# Properties of cpuset node
def delprop(self):
raise AttributeError, "deletion of properties not allowed"
def getcpus(self):
f = file(CpuSet.basepath+self.path+"/cpus")
return f.readline()[:-1]
def setcpus(self, newval):
cpuspec_check(newval)
f = file(CpuSet.basepath+self.path+"/cpus",'w')
f.write(str(newval))
f.close()
log.debug("-> prop_set %s.cpus = %s", self.path, newval)
cpus = property(fget=getcpus, fset=setcpus, fdel=delprop, doc="CPU specifier")
def getmems(self):
f = file(CpuSet.basepath+self.path+"/mems")
return f.readline()[:-1]
def setmems(self, newval):
# FIXME: check format for correctness
f = file(CpuSet.basepath+self.path+"/mems",'w')
f.write(str(newval))
f.close()
log.debug("-> prop_set %s.mems = %s", self.path, newval)
mems = property(getmems, setmems, delprop, "Mem node specifier")
def getcpuxlsv(self):
f = file(CpuSet.basepath+self.path+"/cpu_exclusive")
if f.readline()[:-1] == '1':
return True
else:
return False
def setcpuxlsv(self, newval):
log.debug("-> prop_set %s.cpu_exclusive = %s", self.path, newval)
f = file(CpuSet.basepath+self.path+"/cpu_exclusive",'w')
if newval:
f.write('1')
else:
f.write('0')
f.close()
cpu_exclusive = property(getcpuxlsv, setcpuxlsv, delprop,
"CPU exclusive flag")
def getmemxlsv(self):
f = file(CpuSet.basepath+self.path+"/mem_exclusive")
if f.readline()[:-1] == '1':
return True
else:
return False
def setmemxlsv(self, newval):
log.debug("-> prop_set %s.mem_exclusive = %s", self.path, newval)
f = file(CpuSet.basepath+self.path+"/mem_exclusive",'w')
if newval:
f.write('1')
else:
f.write('0')
f.close()
mem_exclusive = property(getmemxlsv, setmemxlsv, delprop,
"Memory exclusive flag")
def gettasks(self):
f = file(CpuSet.basepath+self.path+"/tasks")
lst = []
for task in f: lst.append(task[:-1])
return lst
def settasks(self, tasklist):
if len(tasklist) > 3:
pb = ProgressBar(len(tasklist), '=')
tick = 0
prog = True
else:
prog = False
for task in tasklist:
try:
f = file(CpuSet.basepath+self.path+"/tasks",'w')
f.write(task)
f.close()
except Exception, err:
if str(err).find('Permission denied') != -1:
raise
# if here, means process is already gone, racy stuff...
if prog:
tick += 1
pb(tick)
log.debug("-> prop_set %s.tasks set with %s tasks", self.path,
len(tasklist))
tasks = property(gettasks, settasks, delprop, "Task list")
#
# Helper functions
#
def unique_set(name):
"""find a unique cpuset by name or path, raise if multiple sets found"""
log.debug("entering unique_set, name=%s", name)
if isinstance(name, CpuSet): return name
nl = find_sets(name)
if len(nl) > 1:
raise CpusetNotUnique('cpuset name "%s" not unique: %s' % (name,
[x.path for x in nl]) )
return nl[0]
def find_sets(name):
"""find cpusets by name or path, return None if not found"""
log = logging.getLogger("cset.find_sets")
log.debug('finding "%s" in cpusets', name)
nodelist = []
if name.find('/') == -1:
log.debug("find by name")
name = name.lower()
if name == 'root':
log.debug("returning root set")
nodelist.append(RootSet)
else:
log.debug("walking from: %s", RootSet.name)
for node in walk_set(RootSet):
if node.name == name:
log.debug('... found node "%s"', name)
nodelist.append(node)
else:
log.debug("find by path")
if name in CpuSet.sets:
log.debug('... found node "%s"', CpuSet.sets[name].name)
nodelist.append(CpuSet.sets[name])
if len(nodelist) == 0:
raise CpusetNotFound('cpuset "%s" not found in cpusets' % name)
return nodelist
def walk_set(set):
""" generator for walking cpuset graph, breadth-first, more or less... """
log = logging.getLogger("cset.walk_set")
for node in set.subsets:
log.debug("+++ yield %s", node.name)
yield node
for node in set.subsets:
for result in walk_set(node):
log.debug("++++++ yield %s", node.name)
yield result
def rescan():
""" re-read the cpuset directory to sync system with data structs """
global RootSet, maxcpu, allcpumask
RootSet = CpuSet()
# figure out system properties
maxcpu = int(RootSet.cpus[-1])
allcpumask = calc_cpumask(maxcpu)
def cpuspec_check(cpuspec):
""" check format of cpuspec for validity """
log.debug("cpuspec_check(%s), maxcpu=%s", cpuspec, maxcpu)
groups = cpuspec.split(',')
if int(groups[-1].split('-')[-1]) > int(maxcpu):
str = 'CPUSPEC "%s" specifies higher max(%s) than available(%s)' % \
(cpuspec, groups[-1].split('-')[-1], maxcpu)
log.debug(str)
raise CpusetException(str)
mo = re.search("[^0-9,\-]", cpuspec)
if mo:
str = 'CPUSPEC "%s" contains invalid charaters: %s' % (cpuspec, mo.group())
log.debug(str)
raise CpusetException(str)
def memspec_check(memspec):
"""check format of memspec for validity """
# FIXME: look under /sys/devices/system/node for numa memory node
# information and check the memspec that way, currently we only do
# a basic check
log.debug("memspec_check(%s)", memspec)
mo = re.search("[^0-9,\-]", memspec)
if mo:
str = 'MEMSPEC "%s" contains invalid charaters: %s' % (memspec, mo.group())
log.debug(str)
raise CpusetException(str)
def cpuspec_inverse(cpuspec):
""" calculate inverse of cpu specification """
cpus = [0 for x in range(maxcpu+1)]
groups = cpuspec.split(',')
log.debug("cpuspec_inverse(%s) maxcpu=%d groups=%d",
cpuspec, maxcpu, len(groups))
for set in groups:
items = set.split('-')
if len(items) == 1:
if not len(items[0]):
# common error of two consecutive commas in cpuspec,
# just ignore it and keep going
continue
cpus[int(items[0])] = 1
elif len(items) == 2:
for x in range(int(items[0]), int(items[1])+1):
cpus[x] = 1
else:
raise CpusetException("cpuspec(%s) has bad group %s" % (cpuspec, set))
log.debug("cpuspec array: %s", cpus)
# calculate inverse of array
for x in range(0, len(cpus)):
if cpus[x] == 0:
cpus[x] = 1
else:
cpus[x] = 0
log.debug(" inverse: %s", cpus)
# build cpuspec expression
nspec = ""
ingrp = False
for x in range(0, len(cpus)):
if cpus[x] == 0 and ingrp:
nspec += str(begin)
if x > begin+1:
if cpus[x] == 1:
nspec += '-' + str(x)
else:
nspec += '-' + str(x-1)
ingrp = False
if cpus[x] == 1:
if not ingrp:
if len(nspec): nspec += ','
begin = x
ingrp = True
if x == len(cpus)-1:
nspec += str(begin)
if x > begin:
nspec += '-' + str(x)
log.debug("inverse cpuspec: %s", nspec)
return nspec
def summary(set):
"""return summary of cpuset with number of tasks running"""
log.debug("entering summary, set=%s", set.path)
return ('"%s" cpuset of: %+10s cpu, with: %+5s tasks running' %
(set.name, set.cpus, len(set.tasks)) )
def calc_cpumask(max):
all = 1
ii = 1
while ii < max+1:
all |= 1 << ii
ii += 1
return "%x" % all
# Test if stand-alone execution
if __name__ == '__main__':
rescan()
# first create them, then find them
try:
os.makedirs(CpuSet.basepath+'/csettest/one/x')
os.mkdir(CpuSet.basepath+'/csettest/one/y')
os.makedirs(CpuSet.basepath+'/csettest/two/x')
os.mkdir(CpuSet.basepath+'/csettest/two/y')
except:
pass
print 'Max cpu on system:', maxcpu
print 'All cpu mask: 0x%s' % allcpumask
print '------- find_sets tests --------'
print 'Find by root of "root" -> ', find_sets("root")
print 'Find by path of "/" -> ', find_sets("/")
print 'Find by path of "/csettest/one" -> ', find_sets("/csettest/one")
print 'Find by name of "one" -> ', find_sets("one")
print 'Find by path of "/csettest/two" -> ', find_sets("/csettest/two")
print 'Find by name of "two" -> ', find_sets("two")
print 'Find by path of "/csettest/one/x" -> ', find_sets("/csettest/one/x")
print 'Find by name of "x" -> ', find_sets("x")
print 'Find by path of "/csettest/two/y" -> ', find_sets("/csettest/two/y")
print 'Find by name of "y" -> ', find_sets("y")
try:
node = find_sets("cantfindmenoway")
print 'Found "cantfindmenoway??!? -> ', node
except CpusetException, err:
print 'Caught exeption for non-existant set (correctly)-> ', err