Skip to content
This repository has been archived by the owner on Oct 23, 2023. It is now read-only.

Commit

Permalink
Add cgroups support
Browse files Browse the repository at this point in the history
Signed-off-by: Timofey Titovets <[email protected]>
  • Loading branch information
nefelim4ag committed Apr 22, 2018
1 parent 264cb09 commit 8899ec0
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 25 deletions.
19 changes: 17 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ 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


default: help

$(PREFIX)/etc/%.cgroups: $(SRC_DIR)/%.cgroups
install -Dm644 $< $@

$(PREFIX)/etc/%.types: $(SRC_DIR)/%.types
install -Dm644 $< $@

Expand All @@ -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
Expand Down
7 changes: 7 additions & 0 deletions ananicy.d/00-cgroups.cgroups
Original file line number Diff line number Diff line change
@@ -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 changes: 2 additions & 2 deletions ananicy.d/00-types.types
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
3 changes: 3 additions & 0 deletions ananicy.d/ananicy.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 156 additions & 21 deletions ananicy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}

Expand All @@ -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):
Expand All @@ -42,6 +114,7 @@ def __init__(self, config_dir="/etc/ananicy.d/", daemon=True):
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"):
Expand Down Expand Up @@ -100,24 +173,6 @@ def __check_disks_schedulers(self):
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
Expand All @@ -132,6 +187,8 @@ def load_config(self):
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:
Expand All @@ -146,6 +203,60 @@ def load_config(self):
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')
Expand Down Expand Up @@ -179,18 +290,23 @@ def get_rule_info(self, line):
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):
Expand Down Expand Up @@ -417,6 +533,17 @@ def process_pid(self, proc, pid):
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
Expand All @@ -432,6 +559,9 @@ def run(self):
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)

Expand All @@ -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)

Expand All @@ -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()

Expand Down

0 comments on commit 8899ec0

Please sign in to comment.