From 9d8de8f1b158baf7825b92a94ea24c963f806808 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 17 Sep 2014 10:11:55 +0300 Subject: [PATCH 1/7] swap limit correction support added --- cgroups/cgroup.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/cgroups/cgroup.py b/cgroups/cgroup.py index 93cc059..733a8c0 100644 --- a/cgroups/cgroup.py +++ b/cgroups/cgroup.py @@ -191,3 +191,26 @@ def memory_limit(self): return value else: return None + + def set_memsw_limit(self,limit=None,unit='megabytes'): + if 'memory' in self.groups: + value = self._format_memory_value(unit,limit) + memory_limit_file = self._get_cgroup_file( + 'memory', 'memory.memsw.limit_in_bytes') + with open(memory_limit_file,'w+') as f: + f.write("%s\n" % value) + else: + raise CgroupsException( + 'MEMORY hierarchy not available in this cgroup') + + @property + def memsw_limit(self): + if 'memory' in self.cgroups: + memory_limit_file = self._get_cgroup_file( + 'memory', 'memory.memsw.limit_in_bytes') + with open(memory_limit_file,'r+') as f: + value = f.read().split('\n')[0] + value = int(int(value)/ 1024 / 1024) + return value + else: + return None From bf0523113f63daa966b3680843381c79165b7eb5 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 17 Sep 2014 14:02:55 +0300 Subject: [PATCH 2/7] cli-tool added. documentation is updated. swap-memory limitation and swappiness configuration support is added --- README.md | 43 ++++++++++++++- cgroups/cgc.py | 130 ++++++++++++++++++++++++++++++++++++++++++++++ cgroups/cgroup.py | 40 +++++++++++--- setup.py | 1 + 4 files changed, 207 insertions(+), 7 deletions(-) create mode 100644 cgroups/cgc.py diff --git a/README.md b/README.md index 0e53131..e915b4f 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,11 @@ import subprocess from cgroups import Cgroup -# First we create the cgroup 'charlie' and we set it's cpu and memory limits +# First we create the cgroup 'charlie' and we set it's cpu, memory and swap limits cg = Cgroup('charlie') cg.set_cpu_limit(50) cg.set_memory_limit(500) +cg.set_memsw_limit(500) # Then we a create a function to add a process in the cgroup def in_my_cgroup(): @@ -176,6 +177,35 @@ If you don't provide an argument to this method, the menthod will set the memory *unit* is the unit used for the limit. Available choices are 'bytes', 'kilobytes', 'megabytes' and 'gigabytes'. Default is 'megabytes'. +**Cgroup.memsw_limit** + +Get the swap-memory limit of the cgroup in bytes + +**Cgroup.set_memsw_limit(limit, unit='megabytes')** + +Set the swap-memory limit of the cgroup. +The function uses the `memory.memsw.limit_in_bytes` hierarchy. + +*limit* is the limit you want to set. +If you don't provide an argument to this method, the menthod will set the memory limit to the default memory limit (ie. no limit) + +*unit* is the unit used for the limit. Available choices are 'bytes', 'kilobytes', 'megabytes' and 'gigabytes'. Default is 'megabytes'. + +**Cgroup.swappiness** + +Get the swappiness option of the cgroup + +**Cgroup.set_swappiness(swappiness=60)** + +swappiness may be in range 1..100 +vm.swappiness = 0 Version 3.5 and over: disables swapping. Prior to version 3.5: The kernel will swap only to avoid an out of memory condition. + +vm.swappiness = 1 Version 3.5 and over: Minimum swappiness without disabling it entirely + +vm.swappiness = 60 The default value. + +vm.swappiness = 100 The kernel will swap aggressively. + ```python from cgroups import Cgroup @@ -187,6 +217,17 @@ cg.set_memory_limit(50) # Reset the limit cg.set_memory_limit('charlie') + +cg.set_swappiness(swappiness=0) + +swappiness = cg.swappiness + +#getting the swap-memory limit of the current cgroup +limit_in_bytes = cg.memsw_limit + +#set the swap-memory limit of the current cgroup +cg.set_memsw_limit(100) + ``` diff --git a/cgroups/cgc.py b/cgroups/cgc.py new file mode 100644 index 0000000..3f2b273 --- /dev/null +++ b/cgroups/cgc.py @@ -0,0 +1,130 @@ +#! /usr/bin/env python +# -*- coding:utf-8 -*- + +from __future__ import unicode_literals +from __future__ import print_function + +import os +import getpass +import logging +import argparse + +import subprocess + +from cgroups.common import BASE_CGROUPS, CgroupsException +from cgroups.user import get_user_info +from cgroups.user import create_user_cgroups + +from cgroups import Cgroup + +logger = logging.getLogger(__name__) + +def run_command_with_cgroups_options(command,cpu=100,mem=-1,swapless=False): + cg_name = '' + if swapless: + cg_name += 'swapless' + cg_name += '_cpu'+str(cpu) + if mem != -1: + cg_name += '_mem'+str(mem) + # print (cg_name) + + cg = Cgroup(cg_name) + cg.set_cpu_limit(cpu) + + if mem != -1: + cg.set_memory_limit(mem) + + if swapless: + cg.set_swappiness(0) + + def preexec_fn (): + pid = os.getpid() + print ("starting {} with pid {}".format(command,pid)) + cg.add(pid) + + process = subprocess.Popen([command],preexec_fn=preexec_fn) + process.wait() + + +def main(): + parser = argparse.ArgumentParser( + prog="cgc", + description="cli tool for managing processes using Control Groups Linux mechanism" + ) + + parser.add_argument( + '-v', + '--verbose', + dest='verbose', + action='store', + help='verbose actions level (DEBUG,INFO,WARN), default=INFO', + default="INFO", + ) + + subparsers = parser.add_subparsers(help='sub-command help') + + parser_run = subparsers.add_parser('run',help='run new process') + parser_run.add_argument( + 'command', + type=str, + help='command to run with some cgroup configuration' + ) + parser_run.add_argument( + '--no-swap', + dest='no_swap', + action='store_true', + help='run command with swap memmory limit set to 0 bytes' + ) + parser_run.add_argument( + '--cpu', + dest='cpu', + action='store', + type=int, + help='run command with some percent of cpu-resource', + default=100, + ) + parser_run.add_argument( + '--mem', + dest='mem', + action='store', + type=int, + help='run command with memory limit', + default=-1 + ) + + parser_add_user = subparsers.add_parser('useradd',help='User to grant privileges to use cgroups') + parser_add_user.add_argument( + 'user', + help='username to grant privileges to use cgroups', + ) + + args = parser.parse_args() + + # Logging + formatter = logging.Formatter( + '%(asctime)s - %(name)s - %(levelname)s - %(message)s') + logstream = logging.StreamHandler() + logstream.setFormatter(formatter) + logger.addHandler(logstream) + if args.verbose == 'DEBUG': + logger.setLevel(logging.DEBUG) + elif args.verbose == 'INFO': + logger.setLevel(logging.INFO) + elif args.verbose == 'WARN': + logger.setLevel(logging.WARN) + else: + logger.setLevel(logging.ERROR) + logger.debug('Logging level: %s' % args.verbose) + + print (args) + + if hasattr(args,'command'): + print ("runing command {}".format(args.command)) + run_command_with_cgroups_options(args.command,args.cpu,args.mem,args.no_swap if args.no_swap else False) + + elif hasattr(args,'user'): + print ("adding user {}".format(args.user)) + create_user_cgroups(args.user) + +if __name__ == "__main__": + main() diff --git a/cgroups/cgroup.py b/cgroups/cgroup.py index 733a8c0..7e99d9d 100644 --- a/cgroups/cgroup.py +++ b/cgroups/cgroup.py @@ -18,6 +18,8 @@ MEMORY_DEFAULT = -1 CPU_DEFAULT = 1024 +SWAPPINESS_DEFAULT = 60 + class Cgroup(object): @@ -193,12 +195,12 @@ def memory_limit(self): return None def set_memsw_limit(self,limit=None,unit='megabytes'): - if 'memory' in self.groups: + if 'memory' in self.cgroups: value = self._format_memory_value(unit,limit) - memory_limit_file = self._get_cgroup_file( + swap_limit_file = self._get_cgroup_file( 'memory', 'memory.memsw.limit_in_bytes') - with open(memory_limit_file,'w+') as f: - f.write("%s\n" % value) + with open(swap_limit_file,'w+') as f: + f.write('%s\n' % value) else: raise CgroupsException( 'MEMORY hierarchy not available in this cgroup') @@ -206,11 +208,37 @@ def set_memsw_limit(self,limit=None,unit='megabytes'): @property def memsw_limit(self): if 'memory' in self.cgroups: - memory_limit_file = self._get_cgroup_file( + swap_limit_file = self._get_cgroup_file( 'memory', 'memory.memsw.limit_in_bytes') - with open(memory_limit_file,'r+') as f: + with open(swap_limit_file,'r+') as f: value = f.read().split('\n')[0] value = int(int(value)/ 1024 / 1024) return value else: return None + + def set_swappiness(self,swappiness=SWAPPINESS_DEFAULT): + if 'memory' in self.cgroups: + swappiness = int(swappiness) + if swappiness<0 and swappiness>100: + raise CgroupsException("swappiness value must be in range 1..100") + value = swappiness + swappiness_file = self._get_cgroup_file( + 'memory', 'memory.swappiness') + with open(swappiness_file,'w+') as f: + f.write('%s\n' % value) + else: + raise CgroupsException( + 'MEMORY hierarchy not available in this cgroup') + + @property + def swappiness(self): + if 'memory' in self.cgroups: + swappiness_file = self._get_cgroup_file( + 'memory', 'memory.swappiness') + with open(swappiness_file,'r+') as f: + value = f.read().split('\n')[0] + value = int(value) + return value + else: + return None diff --git a/setup.py b/setup.py index 2365f53..95a82c9 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ entry_points={ 'console_scripts': [ 'user_cgroups = cgroups.user:main', + 'cgc = cgroups.cgc:main' ] }, ) From 67509e7fed896d88dedc7196466f8e4499a97e9c Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 17 Sep 2014 14:40:31 +0300 Subject: [PATCH 3/7] cli docs updated --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index e915b4f..197dbeb 100644 --- a/README.md +++ b/README.md @@ -248,3 +248,10 @@ from cgroups import Cgroup cg = Cgroup('charlie') cg.delete() ``` + +**Console tool cgc** +```bash +sudo cgc useradd $(whoami) + +cgc run bash --cpu 10 --mem 20 --no-swap +``` From c3024e2aada1f2822370fb9c5587fd54ca7a9827 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 17 Sep 2014 15:39:27 +0300 Subject: [PATCH 4/7] some documentation update --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 197dbeb..1358fc4 100644 --- a/README.md +++ b/README.md @@ -179,11 +179,11 @@ If you don't provide an argument to this method, the menthod will set the memory **Cgroup.memsw_limit** -Get the swap-memory limit of the cgroup in bytes +Get the swap+memory limit of the cgroup in bytes **Cgroup.set_memsw_limit(limit, unit='megabytes')** -Set the swap-memory limit of the cgroup. +Set the swap+memory limit of the cgroup. The function uses the `memory.memsw.limit_in_bytes` hierarchy. *limit* is the limit you want to set. @@ -191,6 +191,9 @@ If you don't provide an argument to this method, the menthod will set the memory *unit* is the unit used for the limit. Available choices are 'bytes', 'kilobytes', 'megabytes' and 'gigabytes'. Default is 'megabytes'. +Note: this paramether can not be less then memory.limit_in_bytes, +because it sums memory and swap usage. + **Cgroup.swappiness** Get the swappiness option of the cgroup From dd391ffa26d4cf2220c0b6b2abe5142474fe10a4 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 17 Sep 2014 17:28:07 +0300 Subject: [PATCH 5/7] cli arguments updated --- cgroups/cgc.py | 41 +++++++++++++++++++---------- cgroups/cgroup.py | 66 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 16 deletions(-) diff --git a/cgroups/cgc.py b/cgroups/cgc.py index 3f2b273..11e42a2 100644 --- a/cgroups/cgc.py +++ b/cgroups/cgc.py @@ -19,19 +19,25 @@ logger = logging.getLogger(__name__) -def run_command_with_cgroups_options(command,cpu=100,mem=-1,swapless=False): - cg_name = '' - if swapless: - cg_name += 'swapless' - cg_name += '_cpu'+str(cpu) - if mem != -1: - cg_name += '_mem'+str(mem) - # print (cg_name) +def run_command_with_cgroups_options(command,cpu=None,mem=None,swapless=False,cgroup=None): + if cgroup is None: + cg_name = '' + if swapless: + cg_name += 'swapless' + cg_name += '_cpu'+str(cpu) + if mem != -1: + cg_name += '_mem'+str(mem) + # print (cg_name) + + else: + cg_name = cgroup cg = Cgroup(cg_name) - cg.set_cpu_limit(cpu) - if mem != -1: + if cpu is not None: + cg.set_cpu_limit(cpu) + + if mem is not None: cg.set_memory_limit(mem) if swapless: @@ -45,7 +51,6 @@ def preexec_fn (): process = subprocess.Popen([command],preexec_fn=preexec_fn) process.wait() - def main(): parser = argparse.ArgumentParser( prog="cgc", @@ -81,7 +86,7 @@ def main(): action='store', type=int, help='run command with some percent of cpu-resource', - default=100, + default=None, ) parser_run.add_argument( '--mem', @@ -89,7 +94,15 @@ def main(): action='store', type=int, help='run command with memory limit', - default=-1 + default=None + ) + parser_run.add_argument( + '--cgroup', + dest='cgroup', + action='store', + type=str, + help='cgroup name', + default=None, ) parser_add_user = subparsers.add_parser('useradd',help='User to grant privileges to use cgroups') @@ -120,7 +133,7 @@ def main(): if hasattr(args,'command'): print ("runing command {}".format(args.command)) - run_command_with_cgroups_options(args.command,args.cpu,args.mem,args.no_swap if args.no_swap else False) + run_command_with_cgroups_options(args.command,cpu=args.cpu,mem=args.mem,swapless=args.no_swap if args.no_swap else False,cgroup=args.cgroup) elif hasattr(args,'user'): print ("adding user {}".format(args.user)) diff --git a/cgroups/cgroup.py b/cgroups/cgroup.py index 7e99d9d..d648669 100644 --- a/cgroups/cgroup.py +++ b/cgroups/cgroup.py @@ -124,7 +124,6 @@ def _format_cpu_value(self, limit=None): value = int(round(CPU_DEFAULT * limit)) return value - def set_cpu_limit(self, limit=None): if 'cpu' in self.cgroups: value = self._format_cpu_value(limit) @@ -170,7 +169,6 @@ def _format_memory_value(self, unit, limit=None): value = limit * 1024 * 1024 * 1024 return value - def set_memory_limit(self, limit=None, unit='megabytes'): if 'memory' in self.cgroups: value = self._format_memory_value(unit, limit) @@ -242,3 +240,67 @@ def swappiness(self): return value else: return None + + @property + def is_under_oom(self): + if 'memory' in self.cgroups: + oom_control_file = self._get_cgroup_file( + 'memory', 'memory.oom_control') + with open(oom_control_file,'r+') as f: + oom_kill_disable,under_oom = map(int,f.read().split('\n')) + oom_kill_disable = True if oom_kill_disable == 1 else False + + under_oom = True if under_oom == 1 else False + + return under_oom + else: + return None + + def set_omm_kill(self,allow=True): + if 'memory' in self.cgroups: + oom_control_file = self._get_cgroup_file( + 'memory', 'memory.oom_control') + oom_kill_disable = 0 if allow else 1 + under_oom = 1 if self.is_under_oom else 0 + with open(oom_control_file,'w+') as f: + f.write("oom_kill_disable %s\nunder_oom %s\n" % (oom_kill_disable,under_oom)) + else: + raise CgroupsException( + 'MEMORY hierarchy not available in this cgroup') + + def allow_kill_under_oom(self): + self.set_omm_kill(allow=True) + + def disallow_kill_under_oom(self): + self.set_omm_kill(allow=False) + + # FREEZER + + @property + def freeze_state(self): + if 'freezer' in self.cgroups: + freezer_state_file = self._get_cgroup_file( + 'freezer', 'freezer.state') + with open(freezer_state_file,'r+') as f: + state = f.read().split('\n')[0] + return state + else: + return None + + def set_freezer_state(self,state): + if state not in ('THAWED','FROZEN'): + raise CgroupsException("there is two values of freezer.state paramener available - THAWED|FROZEN.") + if 'freezer' in self.cgroups: + freezer_state_file = self._get_cgroup_file( + 'freezer', 'freezer.state') + with open(freezer_state_file,'w+') as f: + f.write("%s\n" % state) + else: + raise CgroupsException( + 'FREEZER hierarchy not available in this cgroup') + + def freeze(self): + self.set_freezer_state(state="FROZEN") + + def unfreeze(self): + self.set_freezer_state(state="THAWED") From a705d7c521a47881bc7022d143bc5ffa4bd9a232 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 17 Sep 2014 17:40:59 +0300 Subject: [PATCH 6/7] reventing to argument parsing without subparsers - it's easier to maintain code --- cgroups/cgc.py | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/cgroups/cgc.py b/cgroups/cgc.py index 11e42a2..af42e82 100644 --- a/cgroups/cgc.py +++ b/cgroups/cgc.py @@ -66,21 +66,27 @@ def main(): default="INFO", ) - subparsers = parser.add_subparsers(help='sub-command help') + parser.add_argument( + 'action', + choices=('useradd','run','cgadd','addpid','rmpid','cgls','frz','ufrz'), + help="action to do", + ) - parser_run = subparsers.add_parser('run',help='run new process') - parser_run.add_argument( - 'command', + parser.add_argument( + '--command', + dest='command', + action='store', type=str, - help='command to run with some cgroup configuration' + help='command to run with some cgroup configuration', + default=None, ) - parser_run.add_argument( + parser.add_argument( '--no-swap', dest='no_swap', action='store_true', help='run command with swap memmory limit set to 0 bytes' ) - parser_run.add_argument( + parser.add_argument( '--cpu', dest='cpu', action='store', @@ -88,7 +94,7 @@ def main(): help='run command with some percent of cpu-resource', default=None, ) - parser_run.add_argument( + parser.add_argument( '--mem', dest='mem', action='store', @@ -96,7 +102,7 @@ def main(): help='run command with memory limit', default=None ) - parser_run.add_argument( + parser.add_argument( '--cgroup', dest='cgroup', action='store', @@ -105,14 +111,18 @@ def main(): default=None, ) - parser_add_user = subparsers.add_parser('useradd',help='User to grant privileges to use cgroups') - parser_add_user.add_argument( - 'user', + parser.add_argument( + '--user', + dest='user', + action='store', help='username to grant privileges to use cgroups', + default=None, ) args = parser.parse_args() + print (args) + # Logging formatter = logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') @@ -131,11 +141,11 @@ def main(): print (args) - if hasattr(args,'command'): + if args.action == "run": print ("runing command {}".format(args.command)) run_command_with_cgroups_options(args.command,cpu=args.cpu,mem=args.mem,swapless=args.no_swap if args.no_swap else False,cgroup=args.cgroup) - elif hasattr(args,'user'): + elif args.action == "useradd": print ("adding user {}".format(args.user)) create_user_cgroups(args.user) From 3eb04750ab2b8271302ef3fe42d9de2b781b9367 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 19 Sep 2014 10:29:54 +0300 Subject: [PATCH 7/7] .gitignore updated --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b23c122..88bd791 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ bin/ include/ lib/ +dist/ .ropeproject/ */htmlcov/ *.egg-info/