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.
546 lines
20 KiB
Python
546 lines
20 KiB
Python
"""Cpuset class and cpuset graph, importing module will create model
|
|
"""
|
|
from __future__ import print_function
|
|
|
|
from builtins import str
|
|
from builtins import range
|
|
from builtins import object
|
|
__copyright__ = """
|
|
Copyright (C) 2007-2010 Novell Inc.
|
|
Copyright (C) 2013-2017 SUSE
|
|
Author: Alex Tsariounov <tsariounov@gmail.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 *
|
|
log = logging.getLogger('cset')
|
|
RootSet = None
|
|
|
|
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 = ''
|
|
cpus_path = '/cpus'
|
|
mems_path = '/mems'
|
|
cpu_exclusive_path = '/cpu_exclusive'
|
|
mem_exclusive_path = '/mem_exclusive'
|
|
tasks_path = '/tasks'
|
|
|
|
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
|
|
|
|
# if mounted as a cgroup controller, switch file name format
|
|
if not os.access(path + CpuSet.cpus_path, os.F_OK):
|
|
CpuSet.cpus_path = '/cpuset.cpus'
|
|
CpuSet.mems_path = '/cpuset.mems'
|
|
CpuSet.cpu_exclusive_path = '/cpuset.cpu_exclusive'
|
|
CpuSet.mem_exclusive_path = '/cpuset.mem_exclusive'
|
|
|
|
# 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 parpath in CpuSet.sets:
|
|
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 path in CpuSet.sets:
|
|
log.debug("the cpuset %s already exists, skipping", path)
|
|
self = CpuSet.sets[path] # questionable....
|
|
return
|
|
cpus = CpuSet.basepath + path + CpuSet.cpus_path
|
|
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 .+")
|
|
cpuset1 = re.compile(r"cpuset (/.+) cpuset .+")
|
|
cgroup1 = re.compile(r"cgroup (/.+) cgroup .+")
|
|
path = None
|
|
f = file("/proc/mounts")
|
|
for line in f:
|
|
res = cpuset.search(line)
|
|
if res:
|
|
path = res.group(1)
|
|
break
|
|
res = cpuset1.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
|
|
if cgroup1.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
|
|
|
|
if not os.access(config.mountpoint, os.F_OK):
|
|
os.mkdir(config.mountpoint)
|
|
ret = os.system("mount -t cpuset none " + config.mountpoint)
|
|
if ret:
|
|
raise CpusetException(
|
|
'mount of cpuset filesystem failed, do you have permission?')
|
|
path = config.mountpoint
|
|
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+CpuSet.cpus_path)
|
|
return f.readline()[:-1]
|
|
def setcpus(self, newval):
|
|
cpuspec_check(newval)
|
|
f = file(CpuSet.basepath+self.path+CpuSet.cpus_path,'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+CpuSet.mems_path)
|
|
return f.readline()[:-1]
|
|
def setmems(self, newval):
|
|
# FIXME: check format for correctness
|
|
f = file(CpuSet.basepath+self.path+CpuSet.mems_path,'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+CpuSet.cpu_exclusive_path)
|
|
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+CpuSet.cpu_exclusive_path,'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+CpuSet.mem_exclusive_path)
|
|
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+CpuSet.mem_exclusive_path,'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+CpuSet.tasks_path)
|
|
lst = []
|
|
for task in f: lst.append(task[:-1])
|
|
return lst
|
|
def settasks(self, tasklist):
|
|
notfound = []
|
|
unmovable = []
|
|
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+CpuSet.tasks_path,'w')
|
|
f.write(task)
|
|
f.close()
|
|
except Exception as err:
|
|
if str(err).find('No such process') != -1:
|
|
notfound.append(task)
|
|
elif str(err).find('Invalid argument'):
|
|
unmovable.append(task)
|
|
else:
|
|
raise
|
|
if prog:
|
|
tick += 1
|
|
pb(tick)
|
|
if len(notfound) > 0:
|
|
log.info('**> %s tasks were not found, so were not moved', len(notfound))
|
|
log.debug(' not found: %s', notfound)
|
|
if len(unmovable) > 0:
|
|
log.info('**> %s tasks are not movable, impossible to move', len(unmovable))
|
|
log.debug(' not movable: %s', unmovable)
|
|
log.debug("-> prop_set %s.tasks set with %s tasks", self.path,
|
|
len(tasklist))
|
|
tasks = property(gettasks, settasks, delprop, "Task list")
|
|
|
|
#
|
|
# Helper functions
|
|
#
|
|
|
|
def lookup_task_from_proc(pid):
|
|
"""lookup the cpuset of the specified pid from proc filesystem"""
|
|
log.debug("entering lookup_task_from_proc, pid = %s", str(pid))
|
|
path = "/proc/"+str(pid)+"/cpuset"
|
|
if os.access(path, os.F_OK):
|
|
set = file(path).readline()[:-1]
|
|
log.debug('lookup_task_from_proc: found task %s cpuset: %s', str(pid), set)
|
|
return set
|
|
# FIXME: add search for threads here...
|
|
raise CpusetException("task ID %s not found, i.e. not running" % str(pid))
|
|
|
|
def lookup_task_from_cpusets(pid):
|
|
"""lookup the cpuset of the specified pid from cpuset filesystem"""
|
|
log.debug("entering lookup_task_from_cpusets, pid = %s", str(pid))
|
|
global RootSet
|
|
if RootSet == None: rescan()
|
|
gotit = None
|
|
if pid in RootSet.tasks:
|
|
gotit = RootSet
|
|
else:
|
|
for node in walk_set(RootSet):
|
|
if pid in node.tasks:
|
|
gotit = node
|
|
break
|
|
if gotit:
|
|
log.debug('lookup_task_from_cpusets: found task %s cpuset: %s', str(pid),
|
|
gotit.path)
|
|
return gotit.path
|
|
raise CpusetException("task ID %s not found, i.e. not running" % str(pid))
|
|
|
|
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 name == None:
|
|
raise CpusetException('unique_set() passed None as arg')
|
|
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, raise CpusetNotFound 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")
|
|
if name == 'root':
|
|
log.debug("returning root set")
|
|
nodelist.append(RootSet)
|
|
else:
|
|
log.debug("walking from: %s", RootSet.path)
|
|
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")
|
|
# make sure that leading slash is used if searching by path
|
|
if name[0] != '/': name = '/' + name
|
|
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"""
|
|
log.debug("entering rescan")
|
|
global RootSet, maxcpu, allcpumask
|
|
RootSet = CpuSet()
|
|
# figure out system properties
|
|
log.debug("rescan: all cpus = %s", RootSet.cpus)
|
|
maxcpu = int(RootSet.cpus.split('-')[-1].split(',')[-1])
|
|
log.debug(" max cpu = %s", maxcpu)
|
|
allcpumask = calc_cpumask(maxcpu)
|
|
log.debug(" allcpumask = %s", allcpumask)
|
|
|
|
def cpuspec_check(cpuspec, usemax=True):
|
|
"""check format of cpuspec for validity"""
|
|
log.debug("cpuspec_check(%s)", cpuspec)
|
|
mo = re.search("[^0-9,\-]", cpuspec)
|
|
if mo:
|
|
str = 'CPUSPEC "%s" contains invalid charaters: %s' % (cpuspec, mo.group())
|
|
log.debug(str)
|
|
raise CpusetException(str)
|
|
groups = cpuspec.split(',')
|
|
if usemax and 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)
|
|
for sub in groups:
|
|
it = sub.split('-')
|
|
if len(it) == 2:
|
|
if len(it[0]) == 0 or len(it[1]) == 0:
|
|
# catches negative numbers
|
|
raise CpusetException('CPUSPEC "%s" has bad group "%s"' % (cpuspec, sub))
|
|
if len(it) > 2:
|
|
raise CpusetException('CPUSPEC "%s" has bad group "%s"' % (cpuspec, sub))
|
|
|
|
def cpuspec_to_hex(cpuspec):
|
|
"""convert a cpuspec to the hexadecimal string representation"""
|
|
log.debug('cpuspec_to_string(%s)', cpuspec)
|
|
cpuspec_check(cpuspec, usemax=False)
|
|
groups = cpuspec.split(',')
|
|
number = 0
|
|
for sub in groups:
|
|
items = sub.split('-')
|
|
if len(items) == 1:
|
|
if not len(items[0]):
|
|
# two consecutive commas in cpuspec
|
|
continue
|
|
# one cpu in this group
|
|
log.debug(" adding cpu %s to result", items[0])
|
|
number |= 1 << int(items[0])
|
|
elif len(items) == 2:
|
|
il = [int(ii) for ii in items]
|
|
if il[1] >= il[0]: rng = list(range(il[0], il[1]+1))
|
|
else: rng = list(range(il[1], il[0]+1))
|
|
log.debug(' group=%s has cpu range of %s', sub, rng)
|
|
for num in rng: number |= 1 << num
|
|
else:
|
|
raise CpusetException('CPUSPEC "%s" has bad group "%s"' % (cpuspec, sub))
|
|
log.debug(' final int number=%s in hex=%x', number, number)
|
|
return '%x' % number
|
|
|
|
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)
|
|
if len(set.tasks) == 1: msg = 'task'
|
|
else: msg = 'tasks'
|
|
return ('"%s" cpuset of CPUSPEC(%s) with %s %s running' %
|
|
(set.name, set.cpus, len(set.tasks), msg) )
|
|
|
|
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 as err:
|
|
print('Caught exeption for non-existant set (correctly)-> ', err)
|
|
|