#! /usr/bin/python3
# -*- python -*-
# -*- coding: utf-8 -*-
#   tuna - Application Tuning GUI
#   Copyright (C) 2008, 2009, 2010, 2011 Red Hat Inc.
#   Arnaldo Carvalho de Melo <acme@redhat.com>
#
#   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.

""" tuna - Application Tuning Program"""

import argparse
import os
import sys
import errno
import re
import fnmatch
import gettext
import locale
from functools import reduce
import tuna.new_eth as ethtool
import tuna.tuna_sched as tuna_sched
import procfs
from tuna import tuna, sysfs
import logging
import time
import shutil

def get_loglevel(level):
    if level.isdigit() and int(level) in range(0,5):
        # logging built-in module levels:
        # 0 - NOTSET
        # 10 - DEBUG
        # 20 - INFO
        # 30 - WARNING
        # 40 - ERROR
        return int(level) * 10
    level_str = level.upper()
    if level_str == "CRITICAL":
        raise ValueError("CRITICAL level is not supported by tuna")
    return level_str

def setup_logging(logger_name):

    logger = logging.getLogger(logger_name)
    logger.setLevel(logging.DEBUG)
    logger.propagate = False
    return logger

def add_handler(loglevel, tofile=False):

    formatter = logging.Formatter('[%(levelname)s] %(message)s')

    if tofile:
        lognum = 1
        date = time.strftime("%Y%m%d")
        while os.path.exists("tuna-{}-{}".format(date, lognum)):
            lognum += 1

        path = "tuna-{}-{}/log/Log".format(date,lognum)
        os.makedirs(os.path.dirname(path), exist_ok=True, mode=0o777)
        handler = logging.FileHandler(path)
    else:
        handler = logging.StreamHandler(sys.stderr)

    handler.setFormatter(formatter)
    handler.setLevel(loglevel)

    return handler

try:
    import inet_diag
    have_inet_diag = True
except:
    have_inet_diag = False

# FIXME: ETOOMANYGLOBALS, we need a class!

nr_cpus = None
ps = None
irqs = None

class HelpMessageParser(argparse.ArgumentParser):
    def error(self, message):
        sys.stderr.write(f'error: {message}\n')
        self.print_help()
        sys.exit(2)

def gen_parser():


    POS = {
            "cpu_list": dict(metavar='CPU-LIST', type=tuna.cpustring_to_list, help="CPU-LIST affected by commands"),
            "thread_list": dict(metavar='THREAD-LIST', type=threadstring_to_list, help="THREAD-LIST affected by commands"),
            "filename": dict(metavar='FILENAME', type=str, help="Save kthreads sched tunables to this file"),
            "profilename": dict(type=str, help="Apply changes described in this file"),
            "run_command": dict(metavar='COMMAND', type=str, help="fork a new process and run the \"COMMAND\""),
            "priority": dict(type=tuna.get_policy_and_rtprio, metavar="POLICY:RTPRIO", help="Set thread scheduler tunables: POLICY and RTPRIO"),
          }

    MODS = {
            "logging": dict(dest='loglevel', metavar='LOG-LEVEL', type=get_loglevel, help="Log application details to file for given LOG-LEVEL"),
            "debug" : dict(action='store_true', dest='debug', help='Print DEBUG level logging details to console'),
            "version": dict(action='version', version='0.19', help="show version"),
            "threads": dict(dest='thread_list', default=[], metavar='THREAD-LIST', type=threadstring_to_list, help="THREAD-LIST affected by commands"),
            "irqs": dict(dest='irq_list', default=[], metavar='IRQ-LIST', type=irqstring_to_list, help="IRQ-LIST affect by commands"),
            "cpus": dict(dest='cpu_list', default=[], metavar='CPU-LIST', type=tuna.cpustring_to_list, help='CPU-LIST affected by commands'),
            "sockets": dict(dest='cpu_list', default=[], metavar='CPU-SOCKET-LIST', type=socketstring_to_list, help="CPU-SOCKET-LIST affected by commands"),
            "show_sockets": dict(action='store_true', help='Show network sockets in use by threads'),
            "cgroups": dict(action='store_true', dest='cgroups', help='Display the processes with the type of cgroups they are in'),
            "spaced": dict(action='store_false', dest='compact', help='Display spaced view for cgroups'),
            "affect_children": dict(action='store_true', help="Operation will affect children threads"),
            "nohz_full": dict(action='store_true', help="CPUs in nohz_full kernel command line will be affected by operations"),
            "no_uthreads": dict(action='store_false', dest='uthreads', help="Operations will not affect user threads"),
            "no_kthreads": dict(action='store_false', dest='kthreads', help="Operations will not affect kernel threads"),
            "disable_perf": dict(action='store_true', help="Explicitly disable usage of perf in GUI for process view"),
            "refresh": dict(default=2500, metavar='MSEC', type=int, help="Refresh the GUI every MSEC milliseconds"),
            "priority": dict(default=(None, None), metavar="POLICY:RTPRIO", type=tuna.get_policy_and_rtprio, help="Set thread scheduler tunables: POLICY and RTPRIO"),
            "background": dict(action='store_true', help="Run command as background task")
         }

    parser = HelpMessageParser(description="tuna - Application Tuning Program")

    parser._positionals.title = "commands"
    parser.add_argument('-v', '--version', **MODS['version'])
    parser.add_argument('-L', '--logging', **MODS['logging'])
    parser.add_argument('-D', '--debug', **MODS['debug'])

    subparser = parser.add_subparsers(dest='command')

    isolate = subparser.add_parser('isolate', description="Move all allowed threads and IRQs away from CPU-LIST",
                                    help="Move all allowed threads and IRQs away from CPU-LIST")
    include = subparser.add_parser('include', description="Allow all threads to run on CPU-LIST",
                                     help="Allow all threads to run on CPU-LIST")
    move = subparser.add_parser('move', description="Move selected entities to CPU-LIST",
                                    help="Move selected entities to CPU-LIST")
    spread = subparser.add_parser('spread', description="Spread selected entities to CPU-LIST",
                                    help="Spread selected entities over CPU-LIST")
    priority = subparser.add_parser('priority', description="Set thread scheduler tunables: POLICY and RTPRIO",
                                    help="Set thread scheduler tunables: POLICY and RTPRIO")
    run = subparser.add_parser('run', description="Fork a new process and run the COMMAND",
                                help="Fork a new process and run the COMMAND")
    save = subparser.add_parser('save', description="Save kthreads sched tunables to FILENAME",
                                help="Save kthreads sched tunables to FILENAME")
    apply = subparser.add_parser('apply', description="Apply changes described in profile",
                                help="Apply changes described in profile")
    show_threads = subparser.add_parser('show_threads', description='Show thread list', help='Show thread list')
    show_irqs = subparser.add_parser('show_irqs', description='Show IRQ list', help='Show IRQ list')
    show_configs = subparser.add_parser('show_configs', description='List preloaded profiles', help='List preloaded profiles')

    what_is = subparser.add_parser('what_is', description='Provides help about selected entities', help='Provides help about selected entities')
    gui = subparser.add_parser('gui', description="Start the GUI", help="Start the GUI")

    isolate_group = isolate.add_mutually_exclusive_group(required=True)
    isolate_group.add_argument('-c', '--cpus', **MODS['cpus'])
    isolate_group.add_argument('-S', '--sockets', **MODS['sockets'])
    isolate_group.add_argument('-N', '--nohz_full', **MODS['nohz_full'])

    include_group = include.add_mutually_exclusive_group(required=True)
    include_group.add_argument('-c', '--cpus', **MODS['cpus'])
    include_group.add_argument('-S', '--sockets', **MODS['sockets'])
    include_group.add_argument('-N', '--nohz_full', **MODS['nohz_full'])

    move_group = move.add_mutually_exclusive_group(required=True)
    move_group.add_argument('-c', '--cpus', **MODS['cpus'])
    move_group.add_argument('-S', '--sockets', **MODS['sockets'])
    move_group.add_argument('-N', '--nohz_full', **MODS['nohz_full'])
    move.add_argument('-t', '--threads', **MODS['threads'])
    move.add_argument('-q', '--irqs', **MODS['irqs'])

    spread_group = spread.add_mutually_exclusive_group(required=True)
    spread_group.add_argument('-c', '--cpus', **MODS['cpus'])
    spread_group.add_argument('-S', '--sockets', **MODS['sockets'])
    spread_group.add_argument('-N', '--nohz_full', **MODS['nohz_full'])
    spread.add_argument('-t', '--threads', **MODS['threads'])
    spread.add_argument('-q', '--irqs', **MODS['irqs'])

    priority.add_argument('priority', **POS['priority'])
    priority.add_argument('-t', '--threads', **MODS['threads'], required=True)
    priority.add_argument('-C', '--affect_children', **MODS['affect_children'])

    run.add_argument('run_command', **POS['run_command'])
    run_group = run.add_mutually_exclusive_group(required=False)
    run_group.add_argument('-c', '--cpus', **MODS['cpus'])
    run_group.add_argument('-S', '--sockets', **MODS['sockets'])
    run_group.add_argument('-N', '--nohz_full', **MODS['nohz_full'])
    run.add_argument('-p', '--priority', **MODS['priority'])
    run.add_argument('-b', '--background', **MODS['background'])

    save.add_argument('filename', **POS['filename'])
    save_group = save.add_mutually_exclusive_group(required=False)
    save_group.add_argument('-c', '--cpus', **MODS['cpus'])
    save_group.add_argument('-S', '--sockets', **MODS['sockets'])
    save_group.add_argument('-N', '--nohz_full', **MODS['nohz_full'])
    save.add_argument('-t', '--threads', **MODS['threads'])

    apply.add_argument('profilename', **POS['profilename'])

    show_threads_group1 = show_threads.add_mutually_exclusive_group(required=False)
    show_threads_group1.add_argument('-c', '--cpus', **MODS['cpus'])
    show_threads_group1.add_argument('-N', '--nohz_full', **MODS['nohz_full'])
    show_threads_group1.add_argument('-S', '--sockets', **MODS['sockets'])
    show_threads_group2 = show_threads.add_mutually_exclusive_group(required=False)
    show_threads_group2.add_argument('-t', '--threads', **MODS['threads'])
    show_threads_group2.add_argument('-q', '--irqs', **MODS['irqs'])
    show_threads.add_argument('-U', '--no_uthreads', **MODS['no_uthreads'])
    show_threads.add_argument('-K', '--no_kthreads', **MODS['no_kthreads'])
    show_threads.add_argument('-C', '--affect_children', **MODS['affect_children'])

    if have_inet_diag:
        show_threads.add_argument('-n', '--show_sockets', **MODS['show_sockets'])
    show_threads.add_argument('-G', '--cgroups', **MODS['cgroups'])
    show_threads.add_argument('-z', '--spaced', **MODS['spaced'])


    show_irqs_group = show_irqs.add_mutually_exclusive_group(required=False)
    show_irqs_group.add_argument('-c', '--cpus', **MODS['cpus'])
    show_irqs_group.add_argument('-N', '--nohz_full', **MODS['nohz_full'])
    show_irqs_group.add_argument('-S', '--sockets', **MODS['sockets'])
    show_irqs.add_argument('-q', '--irqs', **MODS['irqs'])

    what_is.add_argument('thread_list', **POS['thread_list'])

    gui.add_argument('-d', '--disable_perf', **MODS['disable_perf'])
    gui.add_argument('-R', '--refresh', **MODS['refresh'])
    gui_group = gui.add_mutually_exclusive_group(required=False)
    gui_group.add_argument('-c', '--cpus', **MODS['cpus'])
    gui_group.add_argument('-N', '--nohz_full', **MODS['nohz_full'])
    gui_group.add_argument('-S', '--sockets', **MODS['sockets'])
    gui.add_argument('-U', '--no_uthreads', **MODS['no_uthreads'])
    gui.add_argument('-K', '--no_kthreads', **MODS['no_kthreads'])

    return parser


def get_nr_cpus():
    """ Get all cpus including disabled cpus """
    global nr_cpus
    if nr_cpus:
        return nr_cpus
    nr_cpus = os.sysconf('SC_NPROCESSORS_CONF')
    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(f"tuna: thread {tid} doesn't exist!")
        return

    pinfo = ps[tid]
    cmdline = procfs.process_cmdline(pinfo)
    help, title = tuna.kthread_help_plain_text(tid, cmdline)
    print(title, "\n\n")
    if help.isspace():
        help = "No help description available."
    print(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=' ')
    print(" %7s" % ("cgroup") if cgroups else "")


def ps_show_sockets(pid, ps, inodes, inode_re, indent=0):
    header_printed = False
    dirname = f"/proc/{pid}/fd"
    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, columns=None, compact=True):
    global irqs
    try:
        affinity = format_affinity(os.sched_getaffinity(pid))
    except OSError as e:
        if e.args[0] == errno.ESRCH:
            return
        raise e

    sched = tuna_sched.sched_str(os.sched_getscheduler(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()
            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)
        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)

    # Indent affected children
    s1 = " %-5d " % pid if affect_children else "  %-5d" % pid
    print(s1, end=' ')
    s2 = "%6s %5d %8s%s %15s     %s" % (sched, rtprio, affinity,
                                     ctxt_switch_info, cmd, users)
    print(s2, end=' ')

    if cgroups:
        length = int(columns) - len(s1 + " ") - len(s2 + " ")
        if len(" %9s" % cgout) <= length:
            print("%s" % cgout)
        else:
            print("\n %s" % cgout + ("" if compact else "\n"))
    else:
        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, columns, compact)


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, compact):

    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 = os.sched_getaffinity(pid)
        except OSError as e:
            if e.args[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()


    # Width of terminal in columns
    columns = 80
    if cgroups:
        if os.isatty(sys.stdout.fileno()):
            columns = shutil.get_terminal_size().columns

    for pid in ps_list:
        ps_show_thread(pid, affect_children, ps, has_ctxt_switch_info,
                       sock_inodes, sock_inode_re, cgroups, columns, compact)


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, compact):
    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, compact)
    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 threadstring_to_list(threadstr):
    global ps
    thread_list = []
    thread_strings = list(set(threadstr.split(',')))
    for s in thread_strings:
        if s.isdigit():
            thread_list.append(int(s))
        else:
            ps = procfs.pidstats()
            try:
                thread_list += ps.find_by_regex(re.compile(fnmatch.translate(s)))
            except:
                thread_list += ps.find_by_name(s)
    return thread_list

def irqstring_to_list(irqstr):

    irq_list = []
    irq_strings = list(set(irqstr.split(',')))
    for s in irq_strings:
        if s.isdigit():
            irq_list.append(int(s))
        else:
            # find_by_user_regex returns a list of strings corresponding to irq number
            irq_list_str = procfs.interrupts().find_by_user_regex(re.compile(fnmatch.translate(s)))
            irq_list += [int(i) for i in irq_list_str if i.isdigit()]
    return irq_list

def socketstring_to_list(socketstr):
    cpu_list = []
    socket_strings = list(set(socketstr.split(',')))
    cpu_info = sysfs.cpus()

    for s in socket_strings:
        if s not in cpu_info.sockets:
            print("tuna: invalid socket %(socket)s sockets available: %(available)s" %
                    {"socket": s,"available": ",".join(list(cpu_info.sockets.keys()))})
            sys.exit(2)
        cpu_list += [int(cpu.name[3:]) for cpu in cpu_info.sockets[s]]
    return cpu_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!"))
            sys.exit(1)
    if config.loadTuna(filename):
        sys.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)
    sys.exit(1)

def nohz_full_to_cpu():

    try:
        return tuna.nohz_full_list()
    except:
        print("tuna: --nohz_full " +
              _(" needs nohz_full=cpulist on the kernel command line"))
        sys.exit(2)

def main():
    global ps

    i18n_init()
    parser = gen_parser()
    # Set all necessary defaults for gui subparser if no arguments provided
    args = parser.parse_args() if len(sys.argv) > 1 else parser.parse_args(['gui'])

    if args.debug:
        my_logger = setup_logging("my_logger")
        my_logger.addHandler(add_handler("DEBUG", tofile=False))
        my_logger.info("Debug option set")

    if args.loglevel:
        if not args.debug:
            my_logger = setup_logging("my_logger")
        try:
            my_logger.addHandler(add_handler(args.loglevel, tofile=True))
            my_logger.info("Logging option set at log level {}".format(args.loglevel))

        except ValueError as e:
            print(e, "tuna: --logging requires valid logging level\n")
            print("Valid log levels: NOTSET, DEBUG, INFO, WARNING, ERROR")
            print("Log levels may be specified numerically (0-4)\n")

    if 'irq_list' in vars(args):
        ps = procfs.pidstats()
        if tuna.has_threaded_irqs(ps):
            for irq in args.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.
                    if 'thread_list' in vars(args):
                        args.thread_list += irq_threads

    if 'nohz_full' in vars(args) and args.nohz_full:
        args.cpu_list = nohz_full_to_cpu()

    if args.command in ['apply', 'a']:
        apply_config(args.profilename)

    elif args.command in ['include', 'I']:
        tuna.include_cpus(args.cpu_list, get_nr_cpus())

    elif args.command in ['isolate', 'i']:
        tuna.isolate_cpus(args.cpu_list, get_nr_cpus())

    elif args.command in ['run', 'r']:

        tuna.run_command(args.run_command, args.priority[0], args.priority[1], args.cpu_list, args.background)

    elif args.command in ['priority', 'p']:

        try:
            tuna.threads_set_priority(args.thread_list, args.priority, args.affect_children)
        except OSError as err:
            print(f"tuna: {err}")
            sys.exit(2)

    elif args.command in ['show_configs']:
        list_config()

    elif args.command in ['show_threads']:
        do_ps(args.thread_list, args.cpu_list, args.irq_list, args.uthreads,
                args.kthreads, args.affect_children, args.show_sockets if "show_sockets" in args else None, args.cgroups, args.compact)

    elif args.command in ['show_irqs']:
        show_irqs(args.irq_list, args.cpu_list)

    elif args.command in ['move', 'm', 'spread', 'x']:
        spread = args.command in ['spread', 'x']

        if not (args.thread_list or args.irq_list):
            parser.error(f"tuna: {args.command} requires a thread/irq list!\n")

        if args.thread_list:
            tuna.move_threads_to_cpu(args.cpu_list, args.thread_list, spread=spread)

        if args.irq_list:
            tuna.move_irqs_to_cpu(args.cpu_list, args.irq_list, spread=spread)

    elif args.command in ['s', 'save']:
        save(args.cpu_list, args.thread_list, args.filename)

    elif args.command in ['W', 'what_is']:
        for tid in args.thread_list:
            thread_help(tid)

    elif args.command in ['g', '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.'))
            parser.print_help()
            return
        except RuntimeError:
            print("tuna: machine needs to be authorized via xhost or ssh -X?")
            return

        try:
            app = tuna_gui.main_gui(args.kthreads, args.uthreads, args.cpu_list, args.refresh, args.disable_perf)
            app.run()
        except KeyboardInterrupt:
            pass


if __name__ == '__main__':
    main()
