#! /usr/bin/python3 # -*- python -*- # -*- coding: utf-8 -*- # tuna - Application Tuning GUI # Copyright (C) 2008, 2009, 2010, 2011 Red Hat Inc. # Arnaldo Carvalho de Melo # # This application 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; version 2. # # This application 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. import getopt, ethtool, fnmatch, errno, os, procfs, re, schedutils, sys from tuna import tuna, sysfs import gettext import locale from functools import reduce try: import inet_diag have_inet_diag = True except: have_inet_diag = False try: set except NameError: # In python < 2.4, "set" is not the first class citizen. from sets import Set as set # FIXME: ETOOMANYGLOBALS, we need a class! nr_cpus = None ps = None irqs = None version = "0.14" def usage(): print(_('Usage: tuna [OPTIONS]')) fmt = '\t%-40s %s' print(fmt % ('-h, --help', _('Give this help list'))) print(fmt % ('-a, --config_file_apply=profilename', _('Apply changes described in profile'))) print(fmt % ('-l, --config_file_list', _('List preloaded profiles'))) print(fmt % ('-g, --gui', _('Start the GUI'))) print(fmt % ('-G, --cgroup', _('Display the processes with the type of cgroups they are in'))) print(fmt % ('-c, --cpus=' + _('CPU-LIST'), _('%(cpulist)s affected by commands') % \ {"cpulist": _('CPU-LIST')})) print(fmt % ('-C, --affect_children', _('Operation will affect children threads'))) print(fmt % ('-f, --filter', _('Display filter the selected entities'))) print(fmt % ('-i, --isolate', _('Move all threads away from %(cpulist)s') % \ {"cpulist": _('CPU-LIST')})) print(fmt % ('-I, --include', _('Allow all threads to run on %(cpulist)s') % \ {"cpulist": _('CPU-LIST')})) print(fmt % ('-K, --no_kthreads', _('Operations will not affect kernel threads'))) print(fmt % ('-m, --move', _('Move selected entities to %(cpulist)s') % \ {"cpulist": _('CPU-LIST')})) print(fmt % ('-N, --nohz_full', _('CPUs in nohz_full= kernel command line will be affected by operations'))) if have_inet_diag: print(fmt % ('-n, --show_sockets', _('Show network sockets in use by threads'))) print(fmt % ('-p, --priority=[' + _('POLICY') + ':]' + _('RTPRIO'), _('Set thread scheduler tunables: %(policy)s and %(rtprio)s') % \ {"policy": _('POLICY'), "rtprio": _('RTPRIO')})) print(fmt % ('-P, --show_threads', _('Show thread list'))) print(fmt % ('-Q, --show_irqs', _('Show IRQ list'))) print(fmt % ('-q, --irqs=' + _('IRQ-LIST'), _('%(irqlist)s affected by commands') % {"irqlist": _('IRQ-LIST')})) print(fmt % ('-r, --run=' + _('COMMAND'), _('fork a new process and run the %(command)s') % \ {"command": _('COMMAND')})) print(fmt % ('-s, --save=' + _('FILENAME'), _('Save kthreads sched tunables to %(filename)s') % \ {"filename": _('FILENAME')})) print(fmt % ('-S, --sockets=' + _('CPU-SOCKET-LIST'), _('%(cpusocketlist)s affected by commands') % \ {"cpusocketlist": _('CPU-SOCKET-LIST')})) print(fmt % ('-t, --threads=' + _('THREAD-LIST'), _('%(threadlist)s affected by commands') % \ {"threadlist": _('THREAD-LIST')})) print(fmt % ('-U, --no_uthreads', _('Operations will not affect user threads'))) print(fmt % ('-v, --version', _('Show version'))) print(fmt % ('-W, --what_is', _('Provides help about selected entities'))) print(fmt % ('-x, --spread', _('Spread selected entities over %(cpulist)s') % \ {"cpulist": _('CPU-LIST')})) def get_nr_cpus(): global nr_cpus if nr_cpus: return nr_cpus nr_cpus = procfs.cpuinfo().nr_cpus return nr_cpus nics = None def get_nics(): global nics if nics: return nics nics = ethtool.get_active_devices() return nics def thread_help(tid): global ps if not ps: ps = procfs.pidstats() if tid not in ps: print("tuna: " + _("thread %d doesn't exists!") % tid) return pinfo = ps[tid] cmdline = procfs.process_cmdline(pinfo) help, title = tuna.kthread_help_plain_text(tid, cmdline) print("%s\n\n%s" % (title, _(help))) def save(cpu_list, thread_list, filename): kthreads = tuna.get_kthread_sched_tunings() for name in list(kthreads.keys()): kt = kthreads[name] if (cpu_list and not set(kt.affinity).intersection(set(cpu_list))) or \ (thread_list and kt.pid not in thread_list) : del kthreads[name] tuna.generate_rtgroups(filename, kthreads, get_nr_cpus()) def ps_show_header(has_ctxt_switch_info,cgroups = False): print("%7s %6s %5s %7s %s" % \ (" ", " ", " ", _("thread"), has_ctxt_switch_info and "ctxt_switches" or "")) print("%7s %6s %5s %7s%s %15s" % \ ("pid", "SCHED_", "rtpri", "affinity", has_ctxt_switch_info and " %9s %12s" % ("voluntary", "nonvoluntary") or "", "cmd"), end=' ') if cgroups: print(" %7s" % ("cgroup")) else: print("") def ps_show_sockets(pid, ps, inodes, inode_re, indent = 0): header_printed = False dirname = "/proc/%s/fd" % pid try: filenames = os.listdir(dirname) except: # Process died return sindent = " " * indent for filename in filenames: pathname = os.path.join(dirname, filename) try: linkto = os.readlink(pathname) except: # Process died continue inode_match = inode_re.match(linkto) if not inode_match: continue inode = int(inode_match.group(1)) if inode not in inodes: continue if not header_printed: print("%s%-10s %-6s %-6s %15s:%-5s %15s:%-5s" % \ (sindent, "State", "Recv-Q", "Send-Q", "Local Address", "Port", "Peer Address", "Port")) header_printed = True s = inodes[inode] print("%s%-10s %-6d %-6d %15s:%-5d %15s:%-5d" % \ (sindent, s.state(), s.receive_queue(), s.write_queue(), s.saddr(), s.sport(), s.daddr(), s.dport())) def format_affinity(affinity): if len(affinity) <= 4: return ",".join(str(a) for a in affinity) return ",".join(str(hex(a)) for a in procfs.hexbitmask(affinity, get_nr_cpus())) def ps_show_thread(pid, affect_children, ps, has_ctxt_switch_info, sock_inodes, sock_inode_re, cgroups): global irqs try: affinity = format_affinity(schedutils.get_affinity(pid)) except (SystemError, OSError) as e: # old python-schedutils incorrectly raised SystemError if e[0] == errno.ESRCH: return raise e sched = schedutils.schedstr(schedutils.get_scheduler(pid))[6:] rtprio = int(ps[pid]["stat"]["rt_priority"]) cgout = ps[pid]["cgroups"] cmd = ps[pid]["stat"]["comm"] users = "" if tuna.is_irq_thread(cmd): try: if not irqs: irqs = procfs.interrupts() if cmd[:4] == "IRQ-": users = irqs[tuna.irq_thread_number(cmd)]["users"] for u in users: if u in get_nics(): users[users.index(u)] = "%s(%s)" % (u, ethtool.get_module(u)) users = ",".join(users) else: u = cmd[cmd.find('-') + 1:] if u in get_nics(): users = ethtool.get_module(u) except: users = "Not found in /proc/interrupts!" ctxt_switch_info = "" if has_ctxt_switch_info: voluntary_ctxt_switches = int(ps[pid]["status"]["voluntary_ctxt_switches"]) nonvoluntary_ctxt_switches = int(ps[pid]["status"]["nonvoluntary_ctxt_switches"]) ctxt_switch_info = " %9d %12s" % (voluntary_ctxt_switches, nonvoluntary_ctxt_switches) if affect_children: print(" %-5d " % pid, end=' ') else: print(" %-5d" % pid, end=' ') print("%6s %5d %8s%s %15s %s" % (sched, rtprio, affinity, ctxt_switch_info, cmd, users), end=' ') if cgroups: print(" %9s" % cgout, end=' ') print("") if sock_inodes: ps_show_sockets(pid, ps, sock_inodes, sock_inode_re, affect_children and 3 or 4) if affect_children and "threads" in ps[pid]: for tid in list(ps[pid]["threads"].keys()): ps_show_thread(tid, False, ps[pid]["threads"], has_ctxt_switch_info, sock_inodes, sock_inode_re, cgroups) def ps_show(ps, affect_children, thread_list, cpu_list, irq_list_numbers, show_uthreads, show_kthreads, has_ctxt_switch_info, sock_inodes, sock_inode_re, cgroups): ps_list = [] for pid in list(ps.keys()): iskth = tuna.iskthread(pid) if not show_uthreads and not iskth: continue if not show_kthreads and iskth: continue in_irq_list = False if irq_list_numbers: if tuna.is_hardirq_handler(ps, pid): try: irq = int(ps[pid]["stat"]["comm"][4:]) if irq not in irq_list_numbers: if not thread_list: continue else: in_irq_list = True except: pass elif not thread_list: continue if not in_irq_list and thread_list and pid not in thread_list: continue try: affinity = schedutils.get_affinity(pid) except (SystemError, OSError) as e: # old python-schedutils incorrectly raised SystemError if e[0] == errno.ESRCH: continue raise e if cpu_list and not set(cpu_list).intersection(set(affinity)): continue ps_list.append(pid) ps_list.sort() for pid in ps_list: ps_show_thread(pid, affect_children, ps, has_ctxt_switch_info, sock_inodes, sock_inode_re, cgroups) def load_socktype(socktype, inodes): idiag = inet_diag.create(socktype = socktype) while True: try: s = idiag.get() except: break inodes[s.inode()] = s def load_sockets(): inodes = {} for socktype in (inet_diag.TCPDIAG_GETSOCK, inet_diag.DCCPDIAG_GETSOCK): load_socktype(socktype, inodes) return inodes def do_ps(thread_list, cpu_list, irq_list, show_uthreads, show_kthreads, affect_children, show_sockets, cgroups): ps = procfs.pidstats() if affect_children: ps.reload_threads() sock_inodes = None sock_inode_re = None if show_sockets: sock_inodes = load_sockets() sock_inode_re = re.compile(r"socket:\[(\d+)\]") has_ctxt_switch_info = "voluntary_ctxt_switches" in ps[1]["status"] try: if sys.stdout.isatty(): ps_show_header(has_ctxt_switch_info, cgroups) ps_show(ps, affect_children, thread_list, cpu_list, irq_list, show_uthreads, show_kthreads, has_ctxt_switch_info, sock_inodes, sock_inode_re, cgroups) except IOError: # 'tuna -P | head' for instance pass def find_drivers_by_users(users): nics = get_nics() drivers = [] for u in users: try: idx = u.index('-') u = u[:idx] except: pass if u in nics: driver = ethtool.get_module(u) if driver not in drivers: drivers.append(driver) return drivers def show_irqs(irq_list, cpu_list): global irqs if not irqs: irqs = procfs.interrupts() if sys.stdout.isatty(): print("%4s %-16s %8s" % ("#", _("users"), _("affinity"),)) sorted_irqs = [] for k in list(irqs.keys()): try: irqn = int(k) affinity = irqs[irqn]["affinity"] except: continue if irq_list and irqn not in irq_list: continue if cpu_list and not set(cpu_list).intersection(set(affinity)): continue sorted_irqs.append(irqn) sorted_irqs.sort() for irq in sorted_irqs: affinity = format_affinity(irqs[irq]["affinity"]) users = irqs[irq]["users"] print("%4d %-16s %8s" % (irq, ",".join(users), affinity), end=' ') drivers = find_drivers_by_users(users) if drivers: print(" %s" % ",".join(drivers)) else: print() def do_list_op(op, current_list, op_list): if not current_list: current_list = [] if op == '+': return list(set(current_list + op_list)) if op == '-': return list(set(current_list) - set(op_list)) return list(set(op_list)) def thread_mapper(s): global ps try: return [ int(s), ] except: pass ps = procfs.pidstats() try: return ps.find_by_regex(re.compile(fnmatch.translate(s))) except: return ps.find_by_name(s) def irq_mapper(s): global irqs try: return [ int(s), ] except: pass if not irqs: irqs = procfs.interrupts() irq_list_str = irqs.find_by_user_regex(re.compile(fnmatch.translate(s))) irq_list = [] for i in irq_list_str: try: irq_list.append(int(i)) except: pass return irq_list def pick_op(argument): if argument == "": return (None, argument) if argument[0] in ('+', '-'): return (argument[0], argument[1:]) return (None, argument) def i18n_init(): (app, localedir) = ('tuna', '/usr/share/locale') locale.setlocale(locale.LC_ALL, '') gettext.bindtextdomain(app, localedir) gettext.textdomain(app) gettext.install(app, localedir) def apply_config(filename): from tuna.config import Config config = Config() if os.path.exists(filename): config.config['root'] = os.getcwd() + "/" filename = os.path.basename(filename) else: if not os.path.exists(config.config['root']+filename): print(filename + _(" not found!")) exit(-1) if config.loadTuna(filename): exit(1) ctrl = 0 values = {} values['toapply'] = {} for index in range(len(config.ctlParams)): for opt in config.ctlParams[index]: values['toapply'][ctrl] = {} values['toapply'][ctrl]['label'] = opt values['toapply'][ctrl]['value'] = config.ctlParams[index][opt] ctrl = ctrl + 1 config.applyChanges(values) def list_config(): from tuna.config import Config config = Config() print(_("Preloaded config files:")) for value in config.populate(): print(value) exit(1) def main(): global ps i18n_init() try: short = "a:c:CfgGhiIKlmNp:PQq:r:s:S:t:UvWx" long = ["cpus=", "affect_children", "filter", "gui", "help", "isolate", "include", "no_kthreads", "move", "nohz_full", "show_sockets", "priority=", "show_threads", "show_irqs", "irqs=", "save=", "sockets=", "threads=", "no_uthreads", "version", "what_is", "spread","cgroup","config_file_apply=","config_file_list=", "run=" ] if have_inet_diag: short += "n" int.append("show_sockets") opts, args = getopt.getopt(sys.argv[1:], short, long) except getopt.GetoptError as err: usage() print(str(err)) sys.exit(2) run_gui = not opts kthreads = True uthreads = True cgroups = False cpu_list = None irq_list = None irq_list_str = None rtprio = None policy = None thread_list = [] thread_list_str = None filter = False affect_children = False show_sockets = False p_waiting_action = False for o, a in opts: if o in ("-h", "--help"): usage() return elif o in ("-a", "--config_file_apply"): apply_config(a) elif o in ("-l", "--config_file_list"): list_config() elif o in ("-c", "--cpus"): (op, a) = pick_op(a) try: op_list = tuna.cpustring_to_list(a) except ValueError: usage() return cpu_list = do_list_op(op, cpu_list, op_list) elif o in ("-N", "--nohz_full"): try: cpu_list = tuna.nohz_full_list() except: print("tuna: --nohz_full " + _(" needs nohz_full=cpulist on the kernel command line")) sys.exit(2) elif o in ("-C", "--affect_children"): affect_children = True elif o in ("-G", "--cgroup"): cgroups = True elif o in ("-t", "--threads"): # The -t - will reset thread list if a == '-': thread_list = [] thread_list_str = '' else: (op, a) = pick_op(a) op_list = reduce(lambda i, j: i + j, list(map(thread_mapper, a.split(",")))) op_list = list(set(op_list)) thread_list = do_list_op(op, thread_list, op_list) # Check if a process name was especified and no # threads was found, which would result in an empty # thread list, i.e. we would print all the threads # in the system when we should print nothing. if not op_list and type(a) == type(''): thread_list_str = do_list_op(op, thread_list_str, a.split(",")) if not op: irq_list = None elif o in ("-f", "--filter"): filter = True elif o in ("-g", "--gui"): run_gui = True elif o in ("-i", "--isolate"): if not cpu_list: print("tuna: --isolate " + _("requires a cpu list!")) sys.exit(2) tuna.isolate_cpus(cpu_list, get_nr_cpus()) elif o in ("-I", "--include"): if not cpu_list: print("tuna: --include " + _("requires a cpu list!")) sys.exit(2) tuna.include_cpus(cpu_list, get_nr_cpus()) elif o in ("-p", "--priority"): # Save policy and rtprio for future Actions (e.g. --run). (policy, rtprio) = tuna.get_policy_and_rtprio(a) if not thread_list: # For backward compatibility p_waiting_action = True else: try: tuna.threads_set_priority(thread_list, a, affect_children) except (SystemError, OSError) as err: # old python-schedutils incorrectly raised SystemError print("tuna: %s" % err) sys.exit(2) elif o in ("-P", "--show_threads"): # If the user specified process names that weren't # resolved to pids, don't show all threads. if not thread_list and not irq_list: if thread_list_str or irq_list_str: continue do_ps(thread_list, cpu_list, irq_list, uthreads, kthreads, affect_children, show_sockets, cgroups) elif o in ("-Q", "--show_irqs"): # If the user specified IRQ names that weren't # resolved to IRQs, don't show all IRQs. if not irq_list and irq_list_str: continue show_irqs(irq_list, cpu_list) elif o in ("-n", "--show_sockets"): show_sockets = True elif o in ("-m", "--move", "-x", "--spread"): if not cpu_list: print("tuna: --move " + _("requires a cpu list!")) sys.exit(2) if not (thread_list or irq_list): print("tuna: --move " + _("requires a list of threads/irqs!")) sys.exit(2) spread = o in ("-x", "--spread") if thread_list: tuna.move_threads_to_cpu(cpu_list, thread_list, spread = spread) if irq_list: tuna.move_irqs_to_cpu(cpu_list, irq_list, spread = spread) elif o in ("-s", "--save"): save(cpu_list, thread_list, a) elif o in ("-S", "--sockets"): (op, a) = pick_op(a) sockets = [socket for socket in a.split(",")] if not cpu_list: cpu_list = [] cpu_info = sysfs.cpus() op_list = [] for socket in sockets: if socket not in cpu_info.sockets: print("tuna: %s" % \ (_("invalid socket %(socket)s sockets available: %(available)s") % \ {"socket": socket, "available": ",".join(list(cpu_info.sockets.keys()))})) sys.exit(2) op_list += [ int(cpu.name[3:]) for cpu in cpu_info.sockets[socket] ] cpu_list = do_list_op(op, cpu_list, op_list) elif o in ("-K", "--no_kthreads"): kthreads = False elif o in ("-q", "--irqs"): (op, a) = pick_op(a) op_list = reduce(lambda i, j: i + j, list(map(irq_mapper, list(set(a.split(",")))))) irq_list = do_list_op(op, irq_list, op_list) # See comment above about thread_list_str if not op_list and type(a) == type(''): irq_list_str = do_list_op(op, irq_list_str, a.split(",")) if not op: thread_list = [] if not ps: ps = procfs.pidstats() if tuna.has_threaded_irqs(ps): for irq in irq_list: irq_re = tuna.threaded_irq_re(irq) irq_threads = ps.find_by_regex(irq_re) if irq_threads: # Change the affinity of the thread too # as we can't rely on changing the irq # affinity changing the affinity of the # thread or vice versa. We need to change # both. thread_list += irq_threads elif o in ("-U", "--no_uthreads"): uthreads = False elif o in ("-v", "--version"): print(version) elif o in ("-W", "--what_is"): if not thread_list: print("tuna: --what_is " + _("requires a thread list!")) sys.exit(2) for tid in thread_list: thread_help(tid) elif o in ("-r", "--run"): # If -p is set, it will be consumed. So, no backward compatible # error handling action must be taken. p_waiting_action = False # pick_op() before run the command: to remove the prefix # + or - from command line. (op, a) = pick_op(a) # In order to include the new process, it must run # the command first, and then get the list of pids, tuna.run_command(a, policy, rtprio, cpu_list) op_list = reduce(lambda i, j: i + j, list(map(thread_mapper, a.split(",")))) op_list = list(set(op_list)) thread_list = do_list_op(op, thread_list, op_list) # Check if a process name was especified and no # threads was found, which would result in an empty # thread list, i.e. we would print all the threads # in the system when we should print nothing. if not op_list and type(a) == type(''): thread_list_str = do_list_op(op, thread_list_str, a.split(",")) if not op: irq_list = None # For backward compatibility: when -p used to be only an Action, it # used to exit(2) if no action was taken (i.e. if no threads_list # was set). if p_waiting_action: print(("tuna: -p ") + _("requires a thread list!")) sys.exit(2) if run_gui: try: from tuna import tuna_gui except ImportError: # gui packages not installed print(_('tuna: packages needed for the GUI missing.')) print(_(' Make sure xauth, pygtk2-libglade are installed.')) usage() return except RuntimeError: print("tuna: machine needs to be authorized via xhost or ssh -X?") return try: cpus_filtered = filter and cpu_list or [] app = tuna_gui.main_gui(kthreads, uthreads, cpus_filtered) app.run() except KeyboardInterrupt: pass if __name__ == '__main__': main()