/proc/[pid]/cgroup

/proc/[pid]/cgroup

If you want to know what cgroups a given process belongs to, check the content of /proc/[pid]/cgroup.

File /proc/[pid]/cgroup has zero or more lines. Each line has such a form:

hierarchy_id:controller_list:cgroup_path

Program proc_cgroup_file.py shows how to obtain such information from /proc/[pid]/cgroup.

from pathlib import Path
from collections import namedtuple

ProcCgroupEntry = namedtuple('ProcCgroupEntry', [
    'hierarchy_id',
    'controller_list',
    'cgroup_path',
])

def parse_proc_cgroup_file(pid):
    path = Path('/proc') / str(pid) / 'cgroup'
    with open(path, 'r') as f:
        for line in f.readlines():
            hierarchy_id, controller_list, cgroup_path = line.strip().split(':')

            yield ProcCgroupEntry(
                int(hierarchy_id),
                controller_list.split(',') if controller_list else [],
                cgroup_path,
            )

if __name__ == '__main__':
    import sys, json
    pid = sys.argv[1]
    print(json.dumps([
        entry._asdict()
        for entry in parse_proc_cgroup_file(pid)
    ]))

Line 0-2, import modules.

Line 3-7, the namedtuple ProcCgroupEntry holds the information of each line in the file /proc/[pid]/cgroup. Each line is comprised of three fields: hierarchy_id, controller_list and cgroup_path.

Line 10, for a given pid, the path to the cgroup proc file is always /proc/[pid]/cgroup.

Line 11-12, to obtain the information from the file /proc/[pid]/cgroup, open it and read line by line.

Line 13-19, each line in the file /proc/[pid]/cgroup is a string of three fields split by commas. After parsing three fields, construct a ProcCgroupEntry object.

Line 21-27, the script can be run with a command-line argument. The output is a list of ProcCgroupEntry objects in JSON format.

The below example demonstrates when you start a container, the process in the container has a corresponding file /proc/[pid]/proc on the host describing the cgroup setup.

# Run the command in one shell.
# It creates a container that has a `bash` process running inside.
$ podman run --rm sleep 86400

# Run the commands in another shell.
# Find the bash process PID and output the cgroup file.
$ ps -ef|grep 'sleep 86400'
cloud_u+   51948   51824  0 08:23 pts/2    00:00:00 podman run -it --rm bash -c sleep 86400
cloud_u+   51979   51969  0 08:23 pts/0    00:00:00 sleep 86400
$ cat /proc/51979/cgroup
12:freezer:/
11:perf_event:/
10:pids:/user.slice/user-1001.slice/session-5.scope
9:cpuset:/
8:net_cls,net_prio:/
7:hugetlb:/
6:blkio:/system.slice/sshd.service
5:devices:/user.slice
4:rdma:/
3:cpu,cpuacct:/
2:memory:/user.slice/user-1001.slice/session-5.scope
1:name=systemd:/user.slice/user-1001.slice/[email protected]/user.slice/podman-51948.scope/2b32e61640d984fcd7795f83e615d4e495ac382b62e0cd47eb32e70fb0d69248

Execute the script:

$ python -mpyoci.cgroup.proc_cgroup_file 51979
[{"hierarchy_id": 12, "controller_list": ["freezer"], "cgroup_path": "/"}, {"hierarchy_id": 11, "controller_list": ["perf_event"], "cgroup_path": "/"}, {"hierarchy_id": 10, "controller_list": ["pids"], "cgroup_path": "/user.slice/user-1001.slice/session-5.scope"}, {"hierarchy_id": 9, "controller_list": ["cpuset"], "cgroup_path": "/"}, {"hierarchy_id": 8, "controller_list": ["net_cls", "net_prio"], "cgroup_path": "/"}, {"hierarchy_id": 7, "controller_list": ["hugetlb"], "cgroup_path": "/"}, {"hierarchy_id": 6, "controller_list": ["blkio"], "cgroup_path": "/system.slice/sshd.service"}, {"hierarchy_id": 5, "controller_list": ["devices"], "cgroup_path": "/user.slice"}, {"hierarchy_id": 4, "controller_list": ["rdma"], "cgroup_path": "/"}, {"hierarchy_id": 3, "controller_list": ["cpu", "cpuacct"], "cgroup_path": "/"}, {"hierarchy_id": 2, "controller_list": ["memory"], "cgroup_path": "/user.slice/user-1001.slice/session-5.scope"}, {"hierarchy_id": 1, "controller_list": ["name=systemd"], "cgroup_path": "/user.slice/user-1001.slice/[email protected]/user.slice/podman-51948.scope/2b32e61640d984fcd7795f83e615d4e495ac382b62e0cd47eb32e70fb0d69248"}]

There are some caveats.

Cgroups can be created in various ways. They can be even totally irrelevant to containers. The cgroups in the given example were created by Podman and systemd.

For cgroups v1, one of the values for controller_list in the example is name=systemd, which seems bizarre. The name=systemd cgroup hierarchy is used by systemd to track services and user sessions. If you’re wondering why you don’t see a list of controllers, the answer is there are not any controllers; in cgroups v1, it is possible a cgroup hierarchy is mounted without attached controllers. It’s purely for tracking processes.

For cgroups v2, hiearchy_id is always 0, and controller_list is always empty, as these two fields are no longer needed.

The field cgroup_path is the pathname relative to the mount point of the hierarchy. For example, for the controller pids and cgroup path /user.slice/user-1001.slice/session-5.scope, we have a mount point in the cgroupfs.

$ ls /sys/fs/cgroup/user.slice/user-1001.slice/session-5.scope