Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
bin/
include/
lib/
dist/
.ropeproject/
*/htmlcov/
*.egg-info/
53 changes: 52 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down Expand Up @@ -176,6 +177,38 @@ 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'.

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

**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
Expand All @@ -187,6 +220,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)

```


Expand All @@ -207,3 +251,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
```
153 changes: 153 additions & 0 deletions cgroups/cgc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#! /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=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)

if cpu is not None:
cg.set_cpu_limit(cpu)

if mem is not None:
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",
)

parser.add_argument(
'action',
choices=('useradd','run','cgadd','addpid','rmpid','cgls','frz','ufrz'),
help="action to do",
)

parser.add_argument(
'--command',
dest='command',
action='store',
type=str,
help='command to run with some cgroup configuration',
default=None,
)
parser.add_argument(
'--no-swap',
dest='no_swap',
action='store_true',
help='run command with swap memmory limit set to 0 bytes'
)
parser.add_argument(
'--cpu',
dest='cpu',
action='store',
type=int,
help='run command with some percent of cpu-resource',
default=None,
)
parser.add_argument(
'--mem',
dest='mem',
action='store',
type=int,
help='run command with memory limit',
default=None
)
parser.add_argument(
'--cgroup',
dest='cgroup',
action='store',
type=str,
help='cgroup name',
default=None,
)

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')
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 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 args.action == "useradd":
print ("adding user {}".format(args.user))
create_user_cgroups(args.user)

if __name__ == "__main__":
main()
117 changes: 115 additions & 2 deletions cgroups/cgroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
MEMORY_DEFAULT = -1
CPU_DEFAULT = 1024

SWAPPINESS_DEFAULT = 60


class Cgroup(object):

Expand Down Expand Up @@ -122,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)
Expand Down Expand Up @@ -168,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)
Expand All @@ -191,3 +191,116 @@ def memory_limit(self):
return value
else:
return None

def set_memsw_limit(self,limit=None,unit='megabytes'):
if 'memory' in self.cgroups:
value = self._format_memory_value(unit,limit)
swap_limit_file = self._get_cgroup_file(
'memory', 'memory.memsw.limit_in_bytes')
with open(swap_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:
swap_limit_file = self._get_cgroup_file(
'memory', 'memory.memsw.limit_in_bytes')
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

@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")
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
entry_points={
'console_scripts': [
'user_cgroups = cgroups.user:main',
'cgc = cgroups.cgc:main'
]
},
)