#!/usr/bin/env python3 # SPDX-License-Identifier: LGPL-2.1-only # # Sample program that shows how to use libcgroup to create a systemd scope # # This program is designed to meet the requirements outlined in the systemd # cmdline example [1] via the libcgroup C APIs. # # [1] https://github.com/libcgroup/libcgroup/blob/main/samples/cmdline/systemd-with-idle-process.md # # # Copyright (c) 2023 Oracle and/or its affiliates. # Author: Tom Hromatka # # # To run this program: # 1. Compile the libcgroup library # $ ./bootstrap.sh # $ make # 2. Note the path to your libcgroup python library # $ pushd src/python/build/lib* # $ export TMPPATH=$(pwd) # $ popd # 2. Run the program # $ # Note that there are more options. Run `create_systemd_scope.py -h` # $ # for more info # $ sudo PYTHONPATH=$TMPPATH \ # ./samples/python/create_systemd_scope.py \ # --slice --scope # from libcgroup import Cgroup, Version, LogLevel import multiprocessing import argparse import signal import time import sys import os TMP_CGNAME = "tmp" HIGH_CGNAME = "high-priority" MED_CGNAME = "medium-priority" LOW_CGNAME = "low-priority" def parse_args(): parser = argparse.ArgumentParser('libcgroup sample program to create a systemd scope') delegated_parser = parser.add_mutually_exclusive_group(required=False) delegated_parser.add_argument('-d', '--delegated', help='Create a delegated scope', action='store_true', dest='delegated') delegated_parser.add_argument('-n', '--notdelegated', help='Create a scope that is not delegated', action='store_false', dest='delegated') parser.set_defaults(delegated=True) parser.add_argument('-p', '--pid', help='PID to place within the scope. If not provided,' 'libcgroup will place a temporary process in the scope', required=False, type=int, default=None) parser.add_argument('-s', '--scope', help='Name of the scope to be created', required=True, type=str, default=None) parser.add_argument('-t', '--slice', help='Name of the slice where the scope will reside', required=True, type=str, default=None) parser.add_argument('-v', '--verbose', help='Enable verbose logging within libcgroup', required=False, action='store_true') args = parser.parse_args() return args def create_scope(args): print('\n----------------------------------------------------------------') print('Creating systemd scope, {}/{},'.format(args.slice, args.scope)) if args.pid: print('and placing PID, {}, in the scope'.format(args.pid)) else: print('and libcgroup will place an idle process in the scope') print('----------------------------------------------------------------\n') Cgroup.create_scope(args.scope, args.slice, args.delegated, pid=args.pid) Cgroup.write_default_systemd_scope(args.slice, args.scope, True) def create_tmp_cgroup(): cg = Cgroup(TMP_CGNAME, Version.CGROUP_V2) cg.create() def move_pids_to_tmp_cgroup(): cg = Cgroup('/', Version.CGROUP_V2) pids = cg.get_processes() for pid in pids: Cgroup.move_process(pid, TMP_CGNAME) def create_high_priority_cgroup(): cg = Cgroup(HIGH_CGNAME, Version.CGROUP_V2) cg.add_setting('cpu.weight', '600') cg.add_setting('memory.low', '1G') cg.create() def create_medium_priority_cgroup(): cg = Cgroup(MED_CGNAME, Version.CGROUP_V2) cg.add_setting('cpu.weight', '300') cg.add_setting('memory.high', '3G') cg.create() def create_low_priority_cgroup(): cg = Cgroup(LOW_CGNAME, Version.CGROUP_V2) cg.add_setting('cpu.weight', '100') cg.add_setting('memory.max', '2G') cg.create() def __infinite_loop(): while True: time.sleep(10) def create_process_in_cgroup(cgname): p = multiprocessing.Process(target=__infinite_loop, ) p.start() Cgroup.move_process(p.pid, cgname) return p.pid def delete_tmp_cgroup(): cg = Cgroup(TMP_CGNAME, Version.CGROUP_V2) pids = cg.get_processes() for pid in pids: os.kill(pid, signal.SIGKILL) # I have observed "Device or resource busy" errors when deleting the # cgroup immediately after the os.kill() instruction. Give the kill # command a little time to succeed time.sleep(1) cg.delete() def main(args): if args.verbose: Cgroup.log_level(LogLevel.CGROUP_LOG_DEBUG) create_scope(args) create_tmp_cgroup() move_pids_to_tmp_cgroup() create_high_priority_cgroup() create_medium_priority_cgroup() create_low_priority_cgroup() high_pid = create_process_in_cgroup(HIGH_CGNAME) med_pid = create_process_in_cgroup(MED_CGNAME) low_pid = create_process_in_cgroup(LOW_CGNAME) delete_tmp_cgroup() print('\n----------------------------------------------------------------') print('Cgroup setup completed successfully') print('\t* The scope {} was placed under slice {}'.format(args.scope, args.slice)) print('\t* Libcgroup initially placed an idle process in the scope,\n' '\t but it has been removed by this program') print('\t* PID {} has been placed in the {} cgroup'.format(high_pid, HIGH_CGNAME)) print('\t* PID {} has been placed in the {} cgroup'.format(med_pid, MED_CGNAME)) print('\t* PID {} has been placed in the {} cgroup'.format(low_pid, LOW_CGNAME)) print('\nThis program will wait for the aforementioned child processes to\n' 'exit before exiting itself. Systemd will automatically delete the\n' 'scope when there are no longer any processes running within the\n' 'scope. Systemd will not automatically delete the slice.') print('----------------------------------------------------------------\n') if __name__ == '__main__': args = parse_args() sys.exit(main(args)) # vim: set et ts=4 sw=4: