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.
573 lines
21 KiB
Python
573 lines
21 KiB
Python
"""Process manipulation command
|
|
"""
|
|
|
|
__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 sys, os, re, logging, pwd, grp
|
|
from optparse import OptionParser, make_option
|
|
|
|
from cpuset import cset
|
|
from cpuset.util import *
|
|
from cpuset.commands.common import *
|
|
try: from cpuset.commands import set
|
|
except: pass
|
|
|
|
global log
|
|
log = logging.getLogger('proc')
|
|
|
|
help = 'create and manage processes within cpusets'
|
|
usage = """%prog [options] [path/program [args]]
|
|
|
|
This command is used to run and manage arbitrary processes on
|
|
specified cpusets. It is also used to move pre-existing processes
|
|
and threads to specified cpusets. You may note there is no
|
|
"kill" or "destroy" option -- use the standard OS ^C or kill
|
|
commands for that.
|
|
|
|
To list which tasks are running in a particular cpuset, use the
|
|
--list command.
|
|
|
|
For example:
|
|
# cset proc --list --set priset
|
|
This command will list all the tasks running in the
|
|
cpuset called "priset".
|
|
|
|
Processes are created by specifying the path to the executable
|
|
and specifying the cpuset that the process is to be created in.
|
|
|
|
For example:
|
|
# cset proc --set=blazing_cpuset --exec /usr/bin/fast_code
|
|
This command will execute the /usr/bin/fast_code program
|
|
on the "blazing_cpuset" cpuset.
|
|
|
|
The PIDSPEC argument taken for the move command is a comma
|
|
separated list of PIDs or TIDs. The list can also include
|
|
brackets of PIDs or TIDs (i.e. tasks) that are inclusive of the
|
|
endpoints.
|
|
|
|
For example:
|
|
1,2,5 Means processes 1, 2 and 5
|
|
1,2,600-700 Means processes 1, 2 and from 600 to 700
|
|
|
|
Note that the range of PIDs or TIDs does not need to have every
|
|
position populated. In other words, for the example above, if
|
|
there is only one process, say PID 57, in the range of 50-65,
|
|
then only that process will be moved.
|
|
|
|
To move a PIDSPEC to a specific cpuset, you can either specify
|
|
the PIDSPEC with --pid and the destination cpuset with --toset,
|
|
or use the short hand and list the cpuset name after the PIDSPEC
|
|
for the --move arguments.
|
|
|
|
The move command accepts multiple common calling methods.
|
|
For example, the following commands are equivalent:
|
|
# cset proc --move 2442,3000-3200 reserved_set
|
|
# cset proc --move --pid=2442,3000-3200 --toset=reserved_set
|
|
These commands move the tasks defined as 2442 and any
|
|
running task between 3000 and 3200 inclusive of the ends
|
|
to the cpuset called "reserved_set".
|
|
|
|
Specifying the --fromset is not necesary since the tasks will be
|
|
moved to the destination cpuset no matter which cpuset they are
|
|
currently running on.
|
|
|
|
Note however that if you do specify a cpuset with the --fromset
|
|
option, then only those tasks that are both in the PIDSPEC *and*
|
|
are running in the cpuset specified by --fromset will be moved.
|
|
I.e., if there is a task running on the system but not in
|
|
--fromset that is in PIDSPEC, it will not be moved.
|
|
|
|
To move all userspace tasks from one cpuset to another, you need
|
|
to specify the source and destination cpuset by name.
|
|
|
|
For example:
|
|
# cset proc --move --fromset=comp1 --toset=comp42
|
|
This command specifies that all processes and threads
|
|
running on cpuset "comp1" be moved to cpuset "comp42".
|
|
|
|
Note that the move command will not move kernel threads unless
|
|
the -k/--kthread switch is specified. If it is, then all unbound
|
|
kernel threads will be added to the move. Unbound kernel threads
|
|
are those that can run on any CPU. If you also specify the
|
|
-a/--all switch, then all tasks, kernel or not, bound or not,
|
|
will be moved.
|
|
|
|
CAUTION: Please be cautious with the --all switch, since moving a
|
|
kernel thread that is bound to a specific CPU to a cpuset that
|
|
does not include that CPU can cause a system hang.
|
|
|
|
You must specify unique cpuset names for the both exec and move
|
|
commands. If a simple name passed to the --fromset, --toset and
|
|
--set parameters is unique on the system then that command
|
|
executes. However, if there are multiple cpusets by that name,
|
|
then you will need to specify which one you mean with a full path
|
|
rooted at the base cpuset tree.
|
|
|
|
For example, suppose you have the following cpuset tree:
|
|
/group1
|
|
/myset
|
|
/yourset
|
|
/group2
|
|
/myset
|
|
/yourset
|
|
|
|
Then, to move a process from myset in group1 to yourset in
|
|
group2, you would have to issue the following command:
|
|
# cset proc --move --pid=50 --fromset=/group1/myset \\
|
|
--toset=/group2/yourset
|
|
"""
|
|
|
|
verbose = 0
|
|
options = [make_option('-l', '--list',
|
|
help = 'list processes in the specified cpuset',
|
|
action = 'store_true'),
|
|
make_option('-e', '--exec',
|
|
help = 'execute arguments in the specified cpuset',
|
|
dest = 'exc',
|
|
action = 'store_true'),
|
|
make_option('-u', '--user',
|
|
help = 'use this USER to --exec (id or name)'),
|
|
make_option('-g', '--group',
|
|
help = 'use this GROUP to --exec (id or name)'),
|
|
make_option('-m', '--move',
|
|
help = 'move specified tasks to specified cpuset; '
|
|
'to move a PIDSPEC to a cpuset, use -m PIDSPEC cpuset; '
|
|
'to move all tasks specify --fromset and --toset',
|
|
action = 'store_true'),
|
|
make_option('-p', '--pid',
|
|
metavar = 'PIDSPEC',
|
|
help = 'specify pid or tid specification'),
|
|
make_option('-s', '--set',
|
|
metavar = 'CPUSET',
|
|
help = 'specify name of immediate cpuset'),
|
|
make_option('-t', '--toset',
|
|
help = 'specify name of destination cpuset'),
|
|
make_option('-f', '--fromset',
|
|
help = 'specify name of origination cpuset'),
|
|
make_option('-k', '--kthread',
|
|
help = 'move, or include moving, unbound kernel threads',
|
|
action = 'store_true'),
|
|
make_option('-a', '--all',
|
|
help = 'force all processes and threads to be moved',
|
|
action = 'store_true'),
|
|
make_option('-v', '--verbose',
|
|
help = 'prints more detailed output, additive',
|
|
action = 'count')
|
|
]
|
|
|
|
def func(parser, options, args):
|
|
log.debug("entering func, options=%s, args=%s", options, args)
|
|
|
|
global verbose
|
|
if options.verbose: verbose = options.verbose
|
|
|
|
cset.rescan()
|
|
|
|
tset = None
|
|
if options.list or options.exc:
|
|
if options.set:
|
|
tset = cset.unique_set(options.set)
|
|
elif options.toset:
|
|
tset = cset.unique_set(options.toset)
|
|
elif len(args) > 0:
|
|
tset = cset.unique_set(args[0])
|
|
if options.exc: del args[0]
|
|
else: tset = args
|
|
else:
|
|
raise CpusetException("cpuset not specified")
|
|
try:
|
|
log.debug("operating on set %s", tset.path)
|
|
except:
|
|
log.debug("operating on sets %s", tset)
|
|
|
|
if options.exc: run(tset, args, options.user, options.group)
|
|
|
|
if options.list:
|
|
list_sets(tset)
|
|
return
|
|
|
|
if options.move or options.kthread:
|
|
# first, we need to know the destination
|
|
tset = None
|
|
if options.toset:
|
|
tset = cset.unique_set(options.toset)
|
|
elif options.set:
|
|
tset = cset.unique_set(options.set)
|
|
elif len(args) > 0:
|
|
if len(args) > 1 and options.pid == None:
|
|
options.pid = args[0]
|
|
tset = cset.unique_set(args[1])
|
|
else:
|
|
tset = cset.unique_set(args[0])
|
|
else:
|
|
raise CpusetException("destination cpuset not specified")
|
|
set.active(tset)
|
|
# next, if there is a pidspec, move just that
|
|
if options.pid:
|
|
fset = None
|
|
if options.fromset and not options.all:
|
|
fset = cset.unique_set(options.fromset)
|
|
pids = pidspec_to_list(options.pid, fset)
|
|
if len(pids):
|
|
l = []
|
|
l.append('--> moving following pidspec: %s' % options.pid)
|
|
l.extend(task_detail_header(' '))
|
|
l.extend(task_detail_table(pids, ' ', 76))
|
|
log.info("\n".join(l))
|
|
selective_move(None, tset, pids, options.kthread, options.all)
|
|
log.info('done')
|
|
else:
|
|
# here we assume move everything from fromset to toset
|
|
if options.fromset == None:
|
|
raise CpusetException("origination cpuset not specified")
|
|
fset = cset.unique_set(options.fromset)
|
|
nt = len(fset.tasks)
|
|
if nt == 0:
|
|
raise CpusetException('no tasks to move from cpuset "%s"'
|
|
% fset.path)
|
|
if options.move:
|
|
log.info('--> moving all tasks from "%s" to "%s"...',
|
|
fset.path, tset.path)
|
|
selective_move(fset, tset, None, options.kthread, options.all)
|
|
else:
|
|
log.info('--> moving all kernel threads from "%s" to "%s"...',
|
|
fset.path, tset.path)
|
|
# this is a -k "move", so only move kernel threads
|
|
pids = []
|
|
for task in fset.tasks:
|
|
try: os.readlink('/proc/'+task+'/exe')
|
|
except: pids.append(task)
|
|
selective_move(fset, tset, pids, options.kthread, options.all)
|
|
log.info('done')
|
|
return
|
|
|
|
# default no options is list
|
|
list_sets(args)
|
|
|
|
def list_sets(args):
|
|
l = []
|
|
if isinstance(args, list):
|
|
for s in args: l.extend(cset.find_sets(s))
|
|
else:
|
|
l.extend(cset.find_sets(args))
|
|
for s in l:
|
|
if len(s.tasks) > 0:
|
|
if verbose:
|
|
log_detailed_task_table(s, ' ')
|
|
else:
|
|
log_detailed_task_table(s, ' ', 76)
|
|
else:
|
|
log.info(cset.summary(s))
|
|
|
|
def move(fromset, toset, plist=None):
|
|
log.debug('entering move, fromset=%s toset=%s list=%s', fromset, toset, plist)
|
|
if isinstance(fromset, str):
|
|
fset = cset.unique_set(fromset)
|
|
elif not isinstance(fromset, cset.CpuSet) and plist == None:
|
|
raise CpusetException(
|
|
"passed fromset=%s, which is not a string or CpuSet" % fromset)
|
|
else:
|
|
fset = fromset
|
|
if isinstance(toset, str):
|
|
tset = cset.unique_set(toset)
|
|
elif not isinstance(toset, cset.CpuSet):
|
|
raise CpusetException(
|
|
"passed toset=%s, which is not a string or CpuSet" % toset)
|
|
else:
|
|
tset = toset
|
|
if plist == None:
|
|
log.debug('moving default of all processes')
|
|
tset.tasks = fset.tasks
|
|
else:
|
|
tset.tasks = plist
|
|
|
|
def selective_move(fset, tset, plist=None, kthread=None, force=None):
|
|
log.debug('entering selective_move, fset=%s tset=%s plist=%s kthread=%s force=%s',
|
|
fset, tset, plist, kthread, force)
|
|
target = cset.unique_set(tset)
|
|
tasks = []
|
|
task_heap = []
|
|
task_check = []
|
|
utsk = 0
|
|
ktsk = 0
|
|
autsk = 0
|
|
aktsk = 0
|
|
utsknr = 0
|
|
ktsknr = 0
|
|
ktskb = 0
|
|
if fset:
|
|
task_check = cset.unique_set(fset).tasks
|
|
if plist:
|
|
task_heap = plist
|
|
else:
|
|
task_heap = cset.unique_set(fset).tasks
|
|
for task in task_heap:
|
|
try:
|
|
# kernel threads do not have an excutable image
|
|
os.readlink('/proc/'+task+'/exe')
|
|
autsk += 1
|
|
if fset and not force:
|
|
try:
|
|
task_check.index(task)
|
|
tasks.append(task)
|
|
utsk += 1
|
|
except:
|
|
log.debug(' task %s not running in %s, skipped',
|
|
task, fset.name)
|
|
utsknr += 1
|
|
else:
|
|
tasks.append(task)
|
|
utsk += 1
|
|
except:
|
|
aktsk += 1
|
|
try:
|
|
# this is in try because the task may not exist by the
|
|
# time we do this, in that case, just ignore it
|
|
if kthread:
|
|
if force:
|
|
tasks.append(task)
|
|
ktsk += 1
|
|
else:
|
|
if is_unbound(task):
|
|
tasks.append(task)
|
|
ktsk += 1
|
|
else:
|
|
log.debug(' kernel thread %s is bound, not adding',
|
|
task)
|
|
ktskb += 1
|
|
except:
|
|
log.debug(' kernel thread %s not found , perhaps it went away',
|
|
task)
|
|
ktsknr += 1
|
|
# ok, move 'em
|
|
log.debug('moving %d tasks to "%s"...', len(tasks), tset.name)
|
|
if len(tasks) == 0:
|
|
log.info('** no task matched move criteria')
|
|
if autsk > 0:
|
|
l = []
|
|
l.append('moving')
|
|
l.append(str(utsk))
|
|
l.append('userspace tasks')
|
|
if utsknr > 0:
|
|
l.append('- not moving')
|
|
l.append(str(utsknr))
|
|
l.append('tasks (not in fromset)')
|
|
log.info(' '.join(l))
|
|
if ktsk > 0 or kthread:
|
|
l = []
|
|
l.append('moving')
|
|
l.append(str(ktsk))
|
|
l.append('kernel threads')
|
|
if ktskb > 0:
|
|
l.append('- not moving')
|
|
l.append(str(ktskb))
|
|
l.append('threads (not unbound)')
|
|
log.info(' '.join(l))
|
|
if aktsk > 0 and force and not kthread and autsk == 0:
|
|
log.info('** not moving kernel threads since both --all and --kthread needed')
|
|
if ktsknr > 0:
|
|
l = []
|
|
l.append('--> not moving')
|
|
l.append(str(ktsknr))
|
|
l.append('tasks because they are missing (race)')
|
|
move(None, target, tasks)
|
|
|
|
def run(tset, args, usr_par=None, grp_par=None):
|
|
if isinstance(tset, str):
|
|
s = cset.unique_set(tset)
|
|
elif not isinstance(tset, cset.CpuSet):
|
|
raise CpusetException(
|
|
"passed set=%s, which is not a string or CpuSet" % tset)
|
|
else:
|
|
s = tset
|
|
log.debug('entering run, set=%s args=%s ', s.path, args)
|
|
set.active(s)
|
|
# check user
|
|
if usr_par:
|
|
try:
|
|
user = pwd.getpwnam(usr_par)[2]
|
|
except KeyError:
|
|
try:
|
|
user = pwd.getpwuid(int(usr_par))[2]
|
|
except:
|
|
raise CpusetException('unknown user: "%s"' % usr_par)
|
|
if grp_par:
|
|
try:
|
|
group = grp.getgrnam(grp_par)[2]
|
|
except KeyError:
|
|
try:
|
|
group = grp.getgrgid(int(grp_par))[2]
|
|
except:
|
|
raise CpusetException('unknown group: "%s"' % grp_par)
|
|
elif usr_par:
|
|
# if user is specified but group is not, and user is not root,
|
|
# then use the users group
|
|
if user != 0:
|
|
try:
|
|
group = grp.getgrnam('users')[2]
|
|
grp_par = True
|
|
except:
|
|
pass # just forget it
|
|
# move myself into target cpuset and exec child
|
|
move_pidspec(str(os.getpid()), s)
|
|
log.info('--> last message, executed args into cpuset "%s", new pid is: %s',
|
|
s.path, os.getpid())
|
|
# change user and group before exec
|
|
if grp_par: os.setgid(group)
|
|
if usr_par: os.setuid(user)
|
|
os.execvp(args[0], args)
|
|
|
|
def is_unbound(proc):
|
|
# FIXME: popen is slow... need to use sched_getaffinity() directly,
|
|
# but python doesn't have it... maybe use pyrex to wrap....
|
|
line = os.popen('/usr/bin/taskset -p ' + str(proc), 'r').readline()
|
|
aff = line.split()[-1]
|
|
log.debug('is_unbound, proc=%s aff=%s allcpumask=%s',
|
|
proc, aff, cset.allcpumask)
|
|
if aff == cset.allcpumask: return True
|
|
return False
|
|
|
|
def pidspec_to_list(pidspec, fset=None):
|
|
log.debug('entering pidspecToList, pidspec=%s', pidspec)
|
|
if fset:
|
|
if isinstance(fset, str): fset = cset.unique_set(fset)
|
|
elif not isinstance(fset, cset.CpuSet):
|
|
raise CpusetException("passed fset=%s, which is not a string or CpuSet" % fset)
|
|
log.debug('from-set specified as: %s', fset.path)
|
|
if not isinstance(pidspec, str):
|
|
raise CpusetException('pidspec=%s is not a string' % pidspec)
|
|
groups = pidspec.split(',')
|
|
plist = []
|
|
if fset: chktsk = fset.tasks
|
|
log.debug('parsing groups: %s', groups)
|
|
for sub in groups:
|
|
items = sub.split('-')
|
|
if len(items) == 1:
|
|
if not len(items[0]):
|
|
# two consecutive commas in pidspec, just continue processing
|
|
continue
|
|
# one pid in this group
|
|
if fset:
|
|
try:
|
|
chktsk.index(items[0])
|
|
plist.append(items[0])
|
|
log.debug(' added single pid: %s', items[0])
|
|
except:
|
|
log.debug(' task %s not running in %s, skipped', items[0], fset.name)
|
|
else:
|
|
plist.append(items[0])
|
|
log.debug(' added single pid: %s', items[0])
|
|
elif len(items) == 2:
|
|
# a range of pids, only include those that exist
|
|
rng = [str(x) for x in range(int(items[0]), int(items[1])+1)
|
|
if os.access('/proc/'+str(x), os.F_OK)]
|
|
if fset:
|
|
for tsk in rng:
|
|
try:
|
|
chktsk.index(tsk)
|
|
plist.append(tsk)
|
|
log.debug(' added task from range: %s', tsk)
|
|
except:
|
|
log.debug(' task %s not running in %s, skipped', tsk, fset.name)
|
|
else:
|
|
plist.extend(rng)
|
|
log.debug(' added range of pids from %s-%s: %s', items[0], items[1], rng)
|
|
else:
|
|
raise CpusetException('pidspec=%s has bad group=%s' % (pidspec, items))
|
|
log.debug('returning parsed pid list: %s', plist)
|
|
log.info('%s tasks match criteria', len(plist))
|
|
return plist
|
|
|
|
def move_pidspec(pidspec, toset, fset=None):
|
|
log.debug('entering move_pidspec, pidspec=%s toset=%s', pidspec, toset)
|
|
if not fset:
|
|
pids = pidspec_to_list(pidspec)
|
|
else:
|
|
# if fromset is specified, only move tasks that are in pidspec
|
|
# and are running in fromset
|
|
log.debug('specified fset=%s', fset)
|
|
pids = pidspec_to_list(pidspec, fset)
|
|
if len(pids) == 0:
|
|
raise CpusetException('tasks do not match all criteria, none moved')
|
|
move(None, toset, pids)
|
|
|
|
def task_detail(pid, width=65):
|
|
# get task details from /proc
|
|
pid = str(pid)
|
|
if not os.access('/proc/'+pid, os.F_OK):
|
|
raise CpusetException('task "%s" does not exist' % pid)
|
|
status = file('/proc/'+pid+'/status', 'r').readlines()
|
|
stdict = {}
|
|
for line in status:
|
|
try:
|
|
stdict[line.split()[0][:-1]] = line.split(':')[1].strip()
|
|
except:
|
|
pass # sometimes, we get an extra \n out of this file...
|
|
cmdline = file('/proc/'+pid+'/cmdline').readline()
|
|
# assume that a zero delimits the cmdline (it does now...)
|
|
cmdline = cmdline.replace('\0', ' ')
|
|
used = 0
|
|
out = pwd.getpwuid(int(stdict['Uid'].split()[0]))[0][:8].ljust(8)
|
|
used += 8
|
|
out += stdict['Pid'].rjust(6)
|
|
used += 6
|
|
out += stdict['PPid'].rjust(6)
|
|
used += 6
|
|
out += stdict['State'].split()[0].center(3)
|
|
used += 3
|
|
try:
|
|
os.readlink('/proc/'+pid+'/exe')
|
|
#prog = stdict['Name'] + ' '.join(cmdline.split()[1:])
|
|
prog = cmdline
|
|
except:
|
|
prog = '['+stdict['Name']+']'
|
|
if width == 0:
|
|
out += prog
|
|
else:
|
|
out += prog[:(width-used)]
|
|
|
|
return out
|
|
|
|
def task_detail_header(indent=None):
|
|
if indent == None: istr = ""
|
|
else: istr = indent
|
|
l = []
|
|
l.append(istr + 'USER PID PPID S TASK NAME')
|
|
l.append(istr + '-------- ----- ----- - ---------')
|
|
return l
|
|
|
|
def task_detail_table(pids, indent=None, width=None):
|
|
l = []
|
|
if indent == None: istr = ""
|
|
else: istr = indent
|
|
for task in pids:
|
|
if width: l.append(istr + task_detail(task, width))
|
|
else: l.append(istr + task_detail(task, 0))
|
|
return l
|
|
|
|
def log_detailed_task_table(set, indent=None, width=None):
|
|
log.debug("entering print_detailed_task_table, set=%s indent=%s width=%s",
|
|
set.path, indent, width)
|
|
l = []
|
|
l.append(cset.summary(set))
|
|
l.extend(task_detail_header(indent))
|
|
l.extend(task_detail_table(set.tasks, indent, width))
|
|
log.info("\n".join(l))
|
|
|