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:
parent
ce3d092628
commit
4daf99fc2c
3 changed files with 50 additions and 21 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue