Browse Source

Add cgroups support

Signed-off-by: Timofey Titovets <nefelim4ag@gmail.com>
pull/96/head
Timofey Titovets 4 years ago
parent
commit
8899ec0297
  1. 19
      Makefile
  2. 7
      ananicy.d/00-cgroups.cgroups
  3. 4
      ananicy.d/00-types.types
  4. 3
      ananicy.d/ananicy.conf
  5. 177
      ananicy.py

19
Makefile

@ -8,6 +8,9 @@ ANANICY_D_R_I := $(patsubst $(SRC_DIR)/%.rules, $(PREFIX)/etc/%.rules, $(ANANICY
ANANICY_D_T := $(shell find $(SRC_DIR)/ananicy.d -type f -name "*.types")
ANANICY_D_T_I := $(patsubst $(SRC_DIR)/%.types, $(PREFIX)/etc/%.types, $(ANANICY_D_T))
ANANICY_D_G := $(shell find $(SRC_DIR)/ananicy.d -type f -name "*.cgroups")
ANANICY_D_G_I := $(patsubst $(SRC_DIR)/%.cgroups, $(PREFIX)/etc/%.cgroups, $(ANANICY_D_G))
A_SERVICE := $(PREFIX)/lib/systemd/system/ananicy.service
A_CONF := $(PREFIX)/etc/ananicy.d/ananicy.conf
A_BIN := $(PREFIX)/usr/bin/ananicy
@ -15,6 +18,9 @@ A_BIN := $(PREFIX)/usr/bin/ananicy
default: help
$(PREFIX)/etc/%.cgroups: $(SRC_DIR)/%.cgroups
install -Dm644 $< $@
$(PREFIX)/etc/%.types: $(SRC_DIR)/%.types
install -Dm644 $< $@
@ -32,11 +38,20 @@ $(A_SERVICE): $(SRC_DIR)/ananicy.service
install: ## Install ananicy
install: $(A_CONF) $(A_BIN) $(A_SERVICE) $(ANANICY_D_R_I) $(ANANICY_D_T_I)
install: $(A_CONF) $(A_BIN)
install: $(A_SERVICE)
install: $(ANANICY_D_G_I)
install: $(ANANICY_D_T_I)
install: $(ANANICY_D_R_I)
uninstall: ## Delete ananicy
uninstall:
@rm -fv $(A_CONF) $(A_BIN) $(A_SERVICE) $(ANANICY_D_R_I)
@rm -fv $(A_CONF)
@rm -rf $(A_BIN)
@rm -rf $(A_SERVICE)
@rm -rf $(ANANICY_D_G_I)
@rm -rf $(ANANICY_D_T_I)
@rm -rf $(ANANICY_D_R_I)
deb: ## Create debian package

7
ananicy.d/00-cgroups.cgroups

@ -0,0 +1,7 @@
# Cgroups definitions
# Currently very simple, only for group CPU intensive tasks
# cpuquota same as systemd CPUQuota,
# only difference is - meaning of N% is all CPUs, not one core.
{ "cgroup": "cpu90", "CPUQuota": 90 }
{ "cgroup": "cpu80", "CPUQuota": 80 }

4
ananicy.d/00-types.types

@ -19,11 +19,11 @@
# Type: BackGround CPU/IO Load
# Background CPU/IO it's needed, but it must be as silent as possible
{ "type": "BG_CPUIO", "nice": 19, "ioclass": "idle", "sched": "idle" }
{ "type": "BG_CPUIO", "nice": 19, "ioclass": "idle", "sched": "idle", "cgroup": "cpu80" }
# Type: Heavy CPU Load
# It must work fast enough but must not create so much noise
{ "type": "Heavy_CPU", "nice": 19, "ioclass": "best-effort", "ionice": 7 }
{ "type": "Heavy_CPU", "nice": 19, "ioclass": "best-effort", "ionice": 7, "cgroup": "cpu90" }
# Type: Chat
{"type": "Chat", "nice": -1, "ioclass": "best-effort", "ionice": 7 }

3
ananicy.d/ananicy.conf

@ -5,10 +5,13 @@
check_freq=5
# Verbose msg: true/false
cgroup_load=true
type_load=true
rule_load=true
apply_nice=true
apply_ioclass=true
apply_ionice=true
apply_sched=true
apply_oom_score_adj=true
apply_cgroup=true

177
ananicy.py

@ -14,8 +14,78 @@ class Failure(Exception):
pass
class CgroupController:
cgroup_fs = "/sys/fs/cgroup/"
type = "cpu,cpuacct"
name = ""
work_path = ""
ncpu = 1
period_us = 100000
quota_us = 100000
files = {}
files_mtime = {}
tasks = {}
def __init__(self, name, cpuquota):
self.ncpu = os.cpu_count()
self.name = name
self.work_path = self.cgroup_fs + self.type + "/" + self.name
if not os.path.exists(self.cgroup_fs):
raise Failure("cgroup fs not mounted")
if not os.path.exists(self.work_path):
os.makedirs(self.work_path)
self.quota_us = self.period_us * self.ncpu * cpuquota / 100
self.files = {
'tasks': self.work_path + "/tasks"
}
open(self.work_path + "/cpu.cfs_period_us", 'w').write(str(self.period_us))
open(self.work_path + "/cpu.cfs_quota_us", 'w').write(str(self.quota_us))
self.files_mtime[self.files["tasks"]] = 0
_thread.start_new_thread(self.__tread_update_tasks, ())
def __tread_update_tasks(self):
tasks_path = self.files["tasks"]
while True:
tasks = {}
while self.files_mtime[tasks_path] == os.path.getmtime(tasks_path):
sleep(1)
self.files_mtime[self.files["tasks"]] = os.path.getmtime(tasks_path)
pids = open(self.files["tasks"], 'r')
for pid in pids.readlines():
pid = int(pid.rstrip())
tasks[pid] = True
self.tasks = tasks
def pid_in_cgroup(self, pid):
tasks = self.tasks
if tasks.get(int(pid)):
return True
else:
return False
def add_pid(self, pid):
try:
open(self.files["tasks"], 'w').write(str(pid))
except OSError:
pass
class Ananicy:
config_dir = None
cgroups = {}
types = {}
rules = {}
@ -24,13 +94,15 @@ class Ananicy:
check_freq = 5
verbose = {
"cgroup_load": True,
"type_load": True,
"rule_load": True,
"apply_nice": True,
"apply_ioclass": True,
"apply_ionice": True,
"apply_sched": True,
"apply_oom_score_adj": True
"apply_oom_score_adj": True,
"apply_cgroup": True
}
def __init__(self, config_dir="/etc/ananicy.d/", daemon=True):
@ -42,6 +114,7 @@ class Ananicy:
if not daemon:
for i in self.verbose:
self.verbose[i] = False
self.load_cgroups()
self.load_types()
self.load_rules()
if os.getenv("NOTIFY_SOCKET"):
@ -100,24 +173,6 @@ class Ananicy:
continue
print("Disk {} not use cfq/bfq scheduler IOCLASS/IONICE will not work on it".format(disk), flush=True)
def get_type_info(self, line):
line = self.__strip_line(line)
if len(line) < 2:
return
line = json.loads(line, parse_int=int)
type = line.get("type")
if type == "":
raise Failure('Missing "type": ')
self.types[type] = {
"nice": self.__check_nice(line.get("nice")),
"ioclass": line.get("ioclass"),
"ionice": self.__check_ionice(line.get("ionice")),
"sched": line.get("sched"),
"oom_score_adj": self.__check_oom_score_adj(line.get("oom_score_adj"))
}
def __YN(self, val):
if val.lower() in ("true", "yes", "1"):
return True
@ -132,6 +187,8 @@ class Ananicy:
if "check_freq=" in col:
check_freq = self.__get_val(col)
self.check_freq = float(check_freq)
if "cgroup_load=" in col:
self.verbose["cgroup_load"] = self.__YN(self.__get_val(col))
if "type_load=" in col:
self.verbose["type_load"] = self.__YN(self.__get_val(col))
if "rule_load=" in col:
@ -146,6 +203,60 @@ class Ananicy:
self.verbose["apply_sched"] = self.__YN(self.__get_val(col))
if "apply_oom_score_adj=" in col:
self.verbose["apply_oom_score_adj"] = self.__YN(self.__get_val(col))
if "apply_cgroup=" in col:
self.verbose["apply_cgroup"] = self.__YN(self.__get_val(col))
def load_cgroups(self):
files = self.find_files(self.config_dir, '.*\\.cgroups')
for file in files:
if self.verbose["cgroup_load"]:
print("Load types:", file)
line_number = 1
for line in open(file).readlines():
try:
self.get_cgroup_info(line)
except Failure as e:
str = "File: {}, Line: {}, Error: {}".format(file, line_number, e)
print(str, flush=True)
except json.decoder.JSONDecodeError as e:
str = "File: {}, Line: {}, Error: {}".format(file, line_number, e)
print(str, flush=True)
line_number += 1
def get_cgroup_info(self, line):
line = self.__strip_line(line)
if len(line) < 2:
return
line = json.loads(line, parse_int=int)
cgroup = line.get("cgroup")
if not cgroup:
raise Failure('Missing "cgroup": ')
cpuquota = line.get("CPUQuota")
if not cpuquota:
raise Failure('Missing "CPUQuota": ')
self.cgroups[cgroup] = CgroupController(cgroup, cpuquota)
def get_type_info(self, line):
line = self.__strip_line(line)
if len(line) < 2:
return
line = json.loads(line, parse_int=int)
type = line.get("type")
if type == "":
raise Failure('Missing "type": ')
self.types[type] = {
"nice": self.__check_nice(line.get("nice")),
"ioclass": line.get("ioclass"),
"ionice": self.__check_ionice(line.get("ionice")),
"sched": line.get("sched"),
"oom_score_adj": self.__check_oom_score_adj(line.get("oom_score_adj")),
"cgroup": line.get("cgroup")
}
def load_types(self):
type_files = self.find_files(self.config_dir, '.*\\.types')
@ -179,18 +290,23 @@ class Ananicy:
if not self.types.get(type):
raise Failure('"type": "{}" not defined'.format(type))
type = self.types[type]
for attr in ("nice", "ioclass", "ionice", "sched", "oom_score_adj"):
for attr in ("nice", "ioclass", "ionice", "sched", "oom_score_adj", "cgroup"):
tmp = type.get(attr)
if tmp:
line[attr] = tmp
cgroup = line.get("cgroup")
if not self.cgroups.get(cgroup):
cgroup = None
self.rules[name] = {
"nice": self.__check_nice(line.get("nice")),
"ioclass": line.get("ioclass"),
"ionice": self.__check_ionice(line.get("ionice")),
"sched": line.get("sched"),
"oom_score_adj": self.__check_oom_score_adj(line.get("oom_score_adj")),
"type": line.get("type")
"type": line.get("type"),
"cgroup": cgroup
}
def load_rules(self):
@ -417,6 +533,17 @@ class Ananicy:
self.sched(proc, pid, rule["sched"])
if rule.get("oom_score_adj"):
self.oom_score_adj(proc, pid, rule["oom_score_adj"])
cgroup = rule.get("cgroup")
if cgroup:
cgroup_ctrl = self.cgroups[cgroup]
if cgroup_ctrl.pid_in_cgroup(pid):
pass
else:
cgroup_ctrl.add_pid(pid)
msg = "Cgroup: {}[{}] added to {}".format(proc[pid]["cmd"], pid, cgroup_ctrl.name)
if self.verbose["apply_cgroup"]:
print(msg)
def processing_rules(self):
proc = self.proc
@ -432,6 +559,9 @@ class Ananicy:
def dump_types(self):
print(json.dumps(self.types, indent=4), flush=True)
def dump_cgroups(self):
print(self.cgroups, flush=True)
def dump_rules(self):
print(json.dumps(self.rules, indent=4), flush=True)
@ -445,6 +575,7 @@ def help():
" start Run script\n",
" dump rules Generate and print rules cache to stdout\n",
" dump types Generate and print types cache to stdout\n",
" dump cgroups Generate and print cgroups cache to stdout\n",
" dump proc Generate and print proc map cache to stdout", flush=True)
exit(0)
@ -460,10 +591,14 @@ def main(argv):
if argv[1] == "dump":
daemon = Ananicy(daemon=False)
if len(argv) < 3:
help()
if argv[2] == "rules":
daemon.dump_rules()
if argv[2] == "types":
daemon.dump_types()
if argv[2] == "cgroups":
daemon.dump_cgroups()
if argv[2] == "proc":
daemon.dump_proc()

Loading…
Cancel
Save