1
0
Fork 0

feat(scheduler): match thread names in addition to process names

This is a first try at matching thread names (comm) in addition to
the process cmdline for changing scheduling policies and priorities
with the `scheduler` plugin.

It adds an optional second regex field to the end of `group.*`
options (defaults to ".*"). The first regex is to match the
process cmdline, the second one is used to match the thread's
comm. If both match, scheduling options are applied.

Note that you can have multiple `group.*` options with the
same process regex and different thread regexes. You can even have
one rule to match a specific thread name, and another with no thread
regex (matching all threads). In this case you need to assign rule
priorities (higher number -> higher priority).

Signed-off-by: Adriaan Schmidt <adriaan.schmidt@siemens.com>
This commit is contained in:
Adriaan Schmidt 2022-08-10 05:05:03 +00:00
parent ce3d092628
commit 4daf99fc2c
3 changed files with 50 additions and 21 deletions

View file

@ -22,7 +22,7 @@ non_isolated_cores=${f:cpulist_invert:${isolated_cores}}
assert2=${f:assertion:isolated_cores contains online CPU(s):${isolated_cores_expanded}:${isolated_cores_online_expanded}} assert2=${f:assertion:isolated_cores contains online CPU(s):${isolated_cores_expanded}:${isolated_cores_online_expanded}}
[scheduler] [scheduler]
# group.group_name=rule_priority:scheduler_policy:scheduler_priority:core_affinity_in_hex:process_name_regex # group.group_name=rule_priority:scheduler_policy:scheduler_priority:core_affinity_in_hex:process_name_regex[:thread_name_regex]
# for i in `pgrep ksoftirqd` ; do grep Cpus_allowed_list /proc/$i/status ; done # for i in `pgrep ksoftirqd` ; do grep Cpus_allowed_list /proc/$i/status ; done
group.ksoftirqd=0:f:2:*:^\[ksoftirqd group.ksoftirqd=0:f:2:*:^\[ksoftirqd
group.ktimers=0:f:2:*:^\[ktimers group.ktimers=0:f:2:*:^\[ktimers

View file

@ -27,7 +27,7 @@ non_isolated_cores=${f:cpulist_invert:${isolated_cores}}
assert2=${f:assertion:isolated_cores contains online CPU(s):${isolated_cores_expanded}:${isolated_cores_online_expanded}} assert2=${f:assertion:isolated_cores contains online CPU(s):${isolated_cores_expanded}:${isolated_cores_online_expanded}}
[scheduler] [scheduler]
# group.group_name=rule_priority:scheduler_policy:scheduler_priority:core_affinity_in_hex:process_name_regex # group.group_name=rule_priority:scheduler_policy:scheduler_priority:core_affinity_in_hex:process_name_regex[:thread_name_regex]
# for i in `pgrep ksoftirqd` ; do grep Cpus_allowed_list /proc/$i/status ; done # for i in `pgrep ksoftirqd` ; do grep Cpus_allowed_list /proc/$i/status ; done
group.ksoftirqd=0:f:2:*:^\[ksoftirqd group.ksoftirqd=0:f:2:*:^\[ksoftirqd
group.ktimers=0:f:2:*:^\[ktimers group.ktimers=0:f:2:*:^\[ktimers

View file

@ -565,10 +565,11 @@ class SchedulerPlugin(base.Plugin):
if not isinstance(process, procfs.process): if not isinstance(process, procfs.process):
pid = process pid = process
process = procfs.process(pid) process = procfs.process(pid)
cmdline = procfs.process_cmdline(process) cmd = procfs.process_cmdline(process)
comm = process['stat']['comm']
if self._is_kthread(process): if self._is_kthread(process):
cmdline = "[" + cmdline + "]" return "[" + cmd + "]", "[" + comm + "]"
return cmdline return cmd, comm
# Raises OSError, IOError # Raises OSError, IOError
def get_processes(self): def get_processes(self):
@ -582,7 +583,7 @@ class SchedulerPlugin(base.Plugin):
processes[pid] = cmd processes[pid] = cmd
if "threads" in proc: if "threads" in proc:
for pid in proc["threads"].keys(): for pid in proc["threads"].keys():
cmd = self._get_cmdline(proc) cmd = self._get_cmdline(pid)
processes[pid] = cmd processes[pid] = cmd
except (OSError, IOError) as e: except (OSError, IOError) as e:
if e.errno == errno.ENOENT \ if e.errno == errno.ENOENT \
@ -817,7 +818,16 @@ class SchedulerPlugin(base.Plugin):
(scheduler, priority) = self._convert_sched_params( (scheduler, priority) = self._convert_sched_params(
scheduler, priority) scheduler, priority)
affinity = self._convert_affinity(affinity) affinity = self._convert_affinity(affinity)
return (rule_prio, scheduler, priority, affinity, regex) # split regex into two, one to match the cmdline, and one the comm
# if there is only one regex for the cmdline, then the comm is matched by ".*"
# allow escaping of colons in regexes (-> '\:' is not a delimiter in the split)
regexes = re.split(r'(?<!\\):', regex, 1)
r1 = regexes[0].replace(r'\:', ':')
try:
r2 = regexes[1].replace(r'\:', ':')
except IndexError:
r2 = ".*"
return (rule_prio, scheduler, priority, affinity, r1, r2)
def _cgroup_create_group(self, cgroup): def _cgroup_create_group(self, cgroup):
path = "%s/%s" % (self._cgroup_mount_point, cgroup) path = "%s/%s" % (self._cgroup_mount_point, cgroup)
@ -934,24 +944,34 @@ class SchedulerPlugin(base.Plugin):
sched_cfg = sorted(buf, key=lambda option_vals: option_vals[1][0]) sched_cfg = sorted(buf, key=lambda option_vals: option_vals[1][0])
sched_all = dict() sched_all = dict()
# for runtime tuning # for runtime tuning
# _sched_lookup is a dict mapping a cmdline regex to a list of scheduling
# parameters, each with a secondary regex to match the comm, sorted from lowest
# to highest priority
instance._sched_lookup = {} instance._sched_lookup = {}
for option, (rule_prio, scheduler, priority, affinity, regex) \ for option, (rule_prio, scheduler, priority, affinity, regex, regex2) \
in sched_cfg: in sched_cfg:
try: try:
r = re.compile(regex) r1 = re.compile(regex)
except re.error as e: except re.error as e:
log.error("error compiling regular expression: '%s'" % str(regex)) log.error("error compiling regular expression: '%s'" % str(regex))
continue continue
processes = [(pid, cmd) for pid, cmd in ps.items() if re.search(r, cmd) is not None] try:
r2 = re.compile(regex2)
except re.error as e:
log.error("error compiling regular expression: '%s'" % str(regex2))
continue
processes = [(pid, cmd) for pid, cmd in ps.items() if re.search(r1, cmd[0]) is not None and re.search(r2, cmd[1]) is not None]
#cmd - process name, option - group name #cmd - process name, option - group name
sched = dict([(pid, (cmd, option, scheduler, priority, affinity, regex)) sched = dict([(pid, (cmd, option, scheduler, priority, affinity))
for pid, cmd in processes]) for pid, cmd in processes])
sched_all.update(sched) sched_all.update(sched)
# make any contained regexes non-capturing: replace "(" with "(?:", # make any contained regexes non-capturing: replace "(" with "(?:",
# unless the "(" is preceded by "\" or followed by "?" # unless the "(" is preceded by "\" or followed by "?"
regex = re.sub(r"(?<!\\)\((?!\?)", "(?:", str(regex)) regex = re.sub(r"(?<!\\)\((?!\?)", "(?:", str(regex))
instance._sched_lookup[regex] = [scheduler, priority, affinity] if not regex in instance._sched_lookup:
for pid, (cmd, option, scheduler, priority, affinity, regex) \ instance._sched_lookup[regex] = []
instance._sched_lookup[regex].append([scheduler, priority, affinity, r2])
for pid, (cmd, option, scheduler, priority, affinity) \
in sched_all.items(): in sched_all.items():
self._tune_process(pid, cmd, scheduler, self._tune_process(pid, cmd, scheduler,
priority, affinity) priority, affinity)
@ -1060,14 +1080,23 @@ class SchedulerPlugin(base.Plugin):
log.error("Failed to get cmdline of PID %d: %s" log.error("Failed to get cmdline of PID %d: %s"
% (pid, e)) % (pid, e))
return return
v = self._cmd.re_lookup(instance._sched_lookup, cmd, r) # first match against cmdline, uses the optimized re_lookup
if v is not None and not pid in self._scheduler_original: vs = self._cmd.re_lookup(instance._sched_lookup, cmd[0], r)
log.debug("tuning new process '%s' with PID '%d' by '%s'" % (cmd, pid, str(v))) if vs is not None and not pid in self._scheduler_original:
(sched, prio, affinity) = v tune_v = None
self._tune_process(pid, cmd, sched, prio, # second match against comm, reversed to start with the highest priority
affinity) for v in reversed(vs):
self._storage.set(self._scheduler_storage_key, if re.search(v[3], cmd[1]) is not None:
self._scheduler_original) tune_v = v[:3]
break
if tune_v is not None:
log.debug("tuning new process '%s' with PID '%d' by '%s'" % (cmd, pid, str(tune_v)))
(sched, prio, affinity) = tune_v
self._tune_process(pid, cmd, sched, prio,
affinity)
self._storage.set(self._scheduler_storage_key,
self._scheduler_original)
def _remove_pid(self, instance, pid): def _remove_pid(self, instance, pid):
if pid in self._scheduler_original: if pid in self._scheduler_original: