From e8fb537645141bc87425b481e48f8484f05725b8 Mon Sep 17 00:00:00 2001 From: hubin Date: Sat, 15 Jul 2023 12:17:36 +0800 Subject: [PATCH] tuned: add app-sensor profile add application plugin and process monitor support for app-sensor profile. if switch to app-sensor, apps given in app-sensor conf file will be monitored, and actions given repectively in app-sensor conf file will be executed when the monitored app starts or quits. Signed-off-by: hubin --- profiles/app-sensor/tuned.conf | 23 ++++++++ tuned/monitors/monitor_process.py | 83 ++++++++++++++++++++++++++++ tuned/plugins/plugin_application.py | 84 +++++++++++++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 profiles/app-sensor/tuned.conf create mode 100644 tuned/monitors/monitor_process.py create mode 100644 tuned/plugins/plugin_application.py diff --git a/profiles/app-sensor/tuned.conf b/profiles/app-sensor/tuned.conf new file mode 100644 index 0000000..771e6ae --- /dev/null +++ b/profiles/app-sensor/tuned.conf @@ -0,0 +1,23 @@ +[main] +summary=dynamic tuning for configured apps + +[application] +# Define list of monitored apps, separated by comma. +# Only apps declared in name will be monitored and execute defined action when app starts and quits. +# Definition syntax: +# name={app1},{app2},... +# for example: +# name=redis,mysql +name= + +# Define action or rollback action for each monitored app. +# No definition or blank action means no action. +# Definition syntax: +# {app}_action={command} +# {app}_rollback_action={command} +# for example: +# redis_action="sysctl -w kernel.sched_min_granularity_ns=10000000" +# redis_rollback_action="sysctl -w kernel.sched_min_granularity_ns=3000000" +# mysql_action= +# mysql_rollback_action= + diff --git a/tuned/monitors/monitor_process.py b/tuned/monitors/monitor_process.py new file mode 100644 index 0000000..524b27a --- /dev/null +++ b/tuned/monitors/monitor_process.py @@ -0,0 +1,83 @@ +import psutil +import tuned.monitors +import tuned.logs + +log = tuned.logs.get() + + +class ProcessMonitor(tuned.monitors.Monitor): + app_program_dict = { + "mysql": ["mysqld"], + "redis": ["redis-server"], + "nginx": ["nginx"], + "unixbench": ["Run"], + "unixbench-arithoh": ["arithoh"], + "unixbench-context1": ["context1"], + "unixbench-dhry2": ["dhry2"], + "unixbench-dhry2reg": ["dhry2reg"], + "unixbench-double": ["double"], + "unixbench-execl": ["execl"], + "unixbench-float": ["float"], + "unixbench-fstime": ["fstime"], + "unixbench-gfx-x11": ["gfx-x11"], + "unixbench-hanoi": ["hanoi"], + "unixbench-int": ["int"], + "unixbench-long": ["long"], + "unixbench-looper": ["looper"], + "unixbench-pipe": ["pipe"], + "unixbench-register": ["register"], + "unixbench-short": ["short"], + "unixbench-spawn": ["spawn"], + "unixbench-syscall": ["syscall"], + "unixbench-whetstone-double": ["whetstone-double"], + "fio": ["fio"], + "iozone": ["iozone"], + "lmbench": ["lmbench"], + "netperf": ["netperf"] + } + + pid_set = set() + pid_app_dict = {} + + @classmethod + def _init_available_devices(cls): + cls._available_devices = set(["application"]) + cls._load["application"] = set() + + @classmethod + def update(cls): + cur_pids = set(psutil.pids()) + prev_pids = cls.pid_set + + # collect new pid and gone pid + new_pids = cur_pids - prev_pids + gone_pids = prev_pids - cur_pids + + # deal with gone pids + if len(gone_pids) > 0: + log.debug(f"find {len(gone_pids)} processes gone") + for pid in gone_pids: + cls.pid_set.remove(pid) + if pid in cls.pid_app_dict: + log.debug(f"app process gone: {cls.pid_app_dict[pid]} (pid {pid})") + cls.pid_app_dict.pop(pid) + + # deal with new pids + if len(new_pids) > 0: + log.debug(f"find {len(new_pids)} processes created") + for pid in new_pids: + try: + process = psutil.Process(pid) + process_name = process.name() + except psutil.NoSuchProcess: + continue + cls.pid_set.add(pid) + # match process name with known applications + for app in cls.app_program_dict: + if process_name in cls.app_program_dict[app]: + cls.pid_app_dict[pid] = app + log.debug(f"app process created: {cls.pid_app_dict[pid]} (pid {pid})") + break + + # output current running applications + cls._load["application"] = set(cls.pid_app_dict.values()) diff --git a/tuned/plugins/plugin_application.py b/tuned/plugins/plugin_application.py new file mode 100644 index 0000000..946d284 --- /dev/null +++ b/tuned/plugins/plugin_application.py @@ -0,0 +1,84 @@ +import subprocess +from . import base +from . import exceptions +import tuned.logs +from tuned.utils.commands import commands + +log = tuned.logs.get() + +ACTION_TIMEOUT = 180 + +class ApplicationPlugin(base.Plugin): + """ + `application`: + + Dynamically executes the optimization action according to the application + running situation. + """ + last_apps_running = set() + + def __init__(self, *args, **kwargs): + super(ApplicationPlugin, self).__init__(*args, **kwargs) + self._has_dynamic_options = True + + def _instance_init(self, instance): + instance._has_static_tuning = False + instance._has_dynamic_tuning = True + # save all options + instance.option_dict = {} + for option, value in list(instance.options.items()): + instance.option_dict[option] = value + log.debug(f"{option}: {value}") + instance._load_monitor = self._monitors_repository.create("process", None) + + def _instance_cleanup(self, instance): + if instance._load_monitor is not None: + self._monitors_repository.delete(instance._load_monitor) + instance._load_monitor = None + instance.option_dict.clear() + + def _instance_update_dynamic(self, instance, device): + if "name" not in instance.option_dict: + return + applications_monitored = set([app.strip() for app in instance.option_dict["name"].split(',')]) + if not applications_monitored: + return + apps_running = applications_monitored.intersection(instance._load_monitor.get_load()["application"]) + log.debug("running: " + str(apps_running)) + new_apps = apps_running - self.last_apps_running + gone_apps = self.last_apps_running - apps_running + if len(new_apps) > 0: + log.info("new apps: " + str(new_apps)) + if len(gone_apps) > 0: + log.info("gone apps: " + str(gone_apps)) + for app in gone_apps: + self._execute_action(instance, app, rollback=True) + for app in new_apps: + self._execute_action(instance, app, rollback=False) + self.last_apps_running = apps_running + + def _instance_unapply_dynamic(self, instance, device): + # restore previous value + pass + + def _execute_action(self, instance, app, rollback=False): + # find action + if rollback: + option = f"{app}_rollback_action" + else: + option = f"{app}_action" + if option not in instance.option_dict: + return + action = str(instance.option_dict[option]) + # remove wrapping " or ' in action + if len(action) >= 2 and (action[0] == '"' or action[0] == "'") and action[0] == action[-1]: + action = action[1:-1] + # execute action for app + if len(action): + log.info(f"{option}: {action}") + try: + p = subprocess.Popen(action, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + retval = p.wait(ACTION_TIMEOUT) + log.info(f"{option}: return {retval}") + except Exception as e: + log.info(f"{option}: {str(e)}") -- 2.33.0