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}}
[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
group.ksoftirqd=0:f:2:*:^\[ksoftirqd
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}}
[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
group.ksoftirqd=0:f:2:*:^\[ksoftirqd
group.ktimers=0:f:2:*:^\[ktimers

View file

@ -565,10 +565,11 @@ class SchedulerPlugin(base.Plugin):
if not isinstance(process, procfs.process):
pid = process
process = procfs.process(pid)
cmdline = procfs.process_cmdline(process)
cmd = procfs.process_cmdline(process)
comm = process['stat']['comm']
if self._is_kthread(process):
cmdline = "[" + cmdline + "]"
return cmdline
return "[" + cmd + "]", "[" + comm + "]"
return cmd, comm
# Raises OSError, IOError
def get_processes(self):
@ -582,7 +583,7 @@ class SchedulerPlugin(base.Plugin):
processes[pid] = cmd
if "threads" in proc:
for pid in proc["threads"].keys():
cmd = self._get_cmdline(proc)
cmd = self._get_cmdline(pid)
processes[pid] = cmd
except (OSError, IOError) as e:
if e.errno == errno.ENOENT \
@ -817,7 +818,16 @@ class SchedulerPlugin(base.Plugin):
(scheduler, priority) = self._convert_sched_params(
scheduler, priority)
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):
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_all = dict()
# 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 = {}
for option, (rule_prio, scheduler, priority, affinity, regex) \
for option, (rule_prio, scheduler, priority, affinity, regex, regex2) \
in sched_cfg:
try:
r = re.compile(regex)
r1 = re.compile(regex)
except re.error as e:
log.error("error compiling regular expression: '%s'" % str(regex))
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
sched = dict([(pid, (cmd, option, scheduler, priority, affinity, regex))
sched = dict([(pid, (cmd, option, scheduler, priority, affinity))
for pid, cmd in processes])
sched_all.update(sched)
# make any contained regexes non-capturing: replace "(" with "(?:",
# unless the "(" is preceded by "\" or followed by "?"
regex = re.sub(r"(?<!\\)\((?!\?)", "(?:", str(regex))
instance._sched_lookup[regex] = [scheduler, priority, affinity]
for pid, (cmd, option, scheduler, priority, affinity, regex) \
if not regex in instance._sched_lookup:
instance._sched_lookup[regex] = []
instance._sched_lookup[regex].append([scheduler, priority, affinity, r2])
for pid, (cmd, option, scheduler, priority, affinity) \
in sched_all.items():
self._tune_process(pid, cmd, scheduler,
priority, affinity)
@ -1060,14 +1080,23 @@ class SchedulerPlugin(base.Plugin):
log.error("Failed to get cmdline of PID %d: %s"
% (pid, e))
return
v = self._cmd.re_lookup(instance._sched_lookup, cmd, r)
if v is not None and not pid in self._scheduler_original:
log.debug("tuning new process '%s' with PID '%d' by '%s'" % (cmd, pid, str(v)))
(sched, prio, affinity) = v
self._tune_process(pid, cmd, sched, prio,
affinity)
self._storage.set(self._scheduler_storage_key,
self._scheduler_original)
# first match against cmdline, uses the optimized re_lookup
vs = self._cmd.re_lookup(instance._sched_lookup, cmd[0], r)
if vs is not None and not pid in self._scheduler_original:
tune_v = None
# second match against comm, reversed to start with the highest priority
for v in reversed(vs):
if re.search(v[3], cmd[1]) is not None:
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):
if pid in self._scheduler_original: