File: cgroup.py

package info (click to toggle)
drgn 0.1.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 7,852 kB
  • sloc: python: 74,992; ansic: 54,589; awk: 423; makefile: 351; sh: 99
file content (149 lines) | stat: -rwxr-xr-x 4,396 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env drgn
# Copyright (c) Meta Platforms, Inc. and affiliates.
# SPDX-License-Identifier: LGPL-2.1-or-later

"""List the paths of all descendants of a cgroup v2"""

import argparse
from contextlib import contextmanager
import os
import sys
from collections import Counter

from drgn import cast
from drgn.helpers.common.type import enum_type_to_class
from drgn.helpers.linux import (
    cgroup_bpf_prog_for_each,
    cgroup_path,
    css_for_each_descendant_pre,
    fget,
    find_task,
)

# Since Linux kernel commit 6fc88c354f3a ("bpf: Migrate cgroup_bpf to internal
# cgroup_bpf_attach_type enum") (in v5.15), the attach type is the
# cgroup-specific enum cgroup_bpf_attach_type. Before that, it was the generic
# enum bpf_attach_type.
try:
    enum_cgroup_bpf_attach_type = prog.type("enum cgroup_bpf_attach_type")
except LookupError:
    CgroupBpfAttachType = enum_type_to_class(
        prog.type("enum bpf_attach_type"),
        "CgroupBpfAttachType",
        exclude=("__MAX_BPF_ATTACH_TYPE",),
    )
else:
    CgroupBpfAttachType = enum_type_to_class(
        enum_cgroup_bpf_attach_type,
        "CgroupBpfAttachType",
        exclude=("CGROUP_BPF_ATTACH_TYPE_INVALID", "MAX_CGROUP_BPF_ATTACH_TYPE",),
    )

CgroupSubsysId = enum_type_to_class(
    prog.type("enum cgroup_subsys_id"),
    "CgroupSubsysId",
    exclude=("CGROUP_SUBSYS_COUNT",),
)

@contextmanager
def open_dir(*args, **kwds):
    # Built-in open() context manager can't deal with directories.
    fd = os.open(*args, **kwds)
    try:
        yield fd
    finally:
        os.close(fd)


def print_cgroup_bpf_progs(cgrp):
    cgroup_printed = False
    for attach_type in CgroupBpfAttachType:
        attach_flags = cgrp.bpf.flags[attach_type.value].value_()
        for prog in cgroup_bpf_prog_for_each(cgrp, attach_type.value):
            prog_id = prog.aux.id.value_()
            prog_name = prog.aux.name.string_().decode()
            if not cgroup_printed:
                print(cgroup_path(cgrp).decode())
                cgroup_printed = True
            print(
                "    {:<8} {:<30} {:<15} {:<15}".format(
                    prog_id, attach_type.name, attach_flags, prog_name
                )
            )


def get_cgroup(path):
    task = find_task(prog, os.getpid())
    try:
        with open_dir(path, os.O_RDONLY) as fd:
            file_ = fget(task, fd)
            kn = cast("struct kernfs_node *", file_.f_path.dentry.d_inode.i_private)
            return cast("struct cgroup *", kn.priv)
    except FileNotFoundError as e:
        raise argparse.ArgumentTypeError(e)


def cmd_tree(cgroup):
    css = cgroup.self.address_of_()

    for pos in css_for_each_descendant_pre(css):
        if not pos.flags & prog["CSS_ONLINE"]:
            continue
        print(cgroup_path(pos.cgroup).decode())


def cmd_bpf(cgroup):
    css = cgroup.self.address_of_()

    for pos in css_for_each_descendant_pre(css):
        if not pos.flags & prog["CSS_ONLINE"]:
            continue
        print_cgroup_bpf_progs(pos.cgroup)


def cmd_stat(cgroup):
    stat = Counter()
    stat_dying = Counter()

    for ssid in CgroupSubsysId:
        css = cgroup.subsys[ssid.value]
        # XXX if subsys of offlined or cgroup rmdir'd under our hands we won't see its subtree
        if not css:
            continue
        for pos in css_for_each_descendant_pre(css):
            stat[ssid] +=1
            if not pos.flags & prog["CSS_ONLINE"]:
                stat_dying[ssid] += 1

    for ssid in CgroupSubsysId:
        if stat[ssid.value] == 0:
            continue
        print("nr_{:<30} {:>4}".format(
            ssid.name,
            stat[ssid.value]
            )
        )
    for ssid in CgroupSubsysId:
        if stat_dying[ssid.value] == 0:
            continue
        print("nr_dying_{:<24} {:>4}".format(
            ssid.name,
            stat_dying[ssid.value]
            )
        )


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("command", choices=["tree", "bpf", "stat"])
    parser.add_argument("cgroups", help="Cgroups", nargs="*", type=get_cgroup)
    args = parser.parse_args()

    if len(args.cgroups) == 0:
        args.cgroups.append(prog["cgrp_dfl_root"].cgrp)

    for cg in args.cgroups:
        if len(args.cgroups) > 1:
            print(cg.kn.name.string_())
        locals()["cmd_" + args.command](cg)