Skip to content

Commit 744de6a

Browse files
committed
Add CgroupV2Info and unified CgroupInfo dispatcher for cgroup V2 support
1 parent 373ef22 commit 744de6a

File tree

3 files changed

+152
-44
lines changed

3 files changed

+152
-44
lines changed

machinestate.py

Lines changed: 76 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python3
2-
# Auto-generated single-file MachineState (2025-10-20 14:37:15)
2+
# Auto-generated single-file MachineState (2025-10-30 13:22:30)
33
# Do not edit manually; edit sources and re-run build_single_py.py
44

55

@@ -1571,24 +1571,82 @@ def __init__(self, extended=False, anonymous=False):
15711571

15721572

15731573
################################################################################
1574-
# Infos about CGroups
1574+
# Helper: detect cgroup v2 and compute base path
15751575
################################################################################
1576-
class CgroupInfo(InfoGroup):
1577-
def __init__(self, extended=False, anonymous=False):
1578-
super(CgroupInfo, self).__init__(name="Cgroups", extended=extended, anonymous=anonymous)
1576+
def _v2_path():
1577+
"""
1578+
Return (is_v2, base_path) for the current process.
1579+
For v2, /proc/self/cgroup has a unified line like '0::/system.slice/...'.
1580+
"""
1581+
try:
1582+
with open("/proc/self/cgroup", "r", encoding="utf-8", errors="ignore") as f:
1583+
lines = f.read().splitlines()
1584+
except Exception:
1585+
return False, "/sys/fs/cgroup"
1586+
1587+
rel = "/"
1588+
is_v2 = False
1589+
for line in lines:
1590+
parts = line.split(":", 2)
1591+
# unified v2 line: controllers field empty, OR hierarchy id "0"
1592+
if len(parts) == 3 and (parts[1] == "" or parts[0] == "0"):
1593+
is_v2 = True
1594+
rel = parts[2]
1595+
break
1596+
base = pjoin("/sys/fs/cgroup", rel.strip("/")) if rel else "/sys/fs/cgroup"
1597+
return is_v2, base
1598+
1599+
1600+
################################################################################
1601+
# v1: keep your original logic (renamed to CgroupV1Info)
1602+
################################################################################
1603+
class CgroupV1Info:
1604+
"""Registrar for cgroup v1 cpuset info (your original logic)."""
1605+
@staticmethod
1606+
def register(into: InfoGroup, extended: bool):
15791607
csetmat = re.compile(r"\d+\:cpuset\:([/\w\d\-\._]*)")
15801608
cset = process_file(("/proc/self/cgroup", csetmat))
1581-
if cset is not None:
1582-
base = pjoin("/sys/fs/cgroup/cpuset", cset.strip("/"))
1583-
self.addf("CPUs", pjoin(base, "cpuset.cpus"), r"(.+)", tointlist)
1584-
self.addf("Mems", pjoin(base, "cpuset.mems"), r"(.+)", tointlist)
1585-
self.required("CPUs", "Mems")
1586-
if extended:
1587-
names = ["CPUs.effective", "Mems.effective"]
1588-
files = ["cpuset.effective_cpus", "cpuset.effective_mems"]
1589-
for key, fname in zip(names, files):
1590-
self.addf(key, pjoin(base, fname), r"(.+)", tointlist)
1591-
self.required(key)
1609+
if not cset:
1610+
return
1611+
base = pjoin("/sys/fs/cgroup/cpuset", cset.strip("/"))
1612+
into.addf("CPUs", pjoin(base, "cpuset.cpus"), r"(.+)", tointlist)
1613+
into.addf("Mems", pjoin(base, "cpuset.mems"), r"(.+)", tointlist)
1614+
into.required("CPUs", "Mems")
1615+
if extended:
1616+
into.addf("CPUs.effective", pjoin(base, "cpuset.effective_cpus"), r"(.+)", tointlist)
1617+
into.addf("Mems.effective", pjoin(base, "cpuset.effective_mems"), r"(.+)", tointlist)
1618+
into.required("CPUs.effective", "Mems.effective")
1619+
1620+
1621+
################################################################################
1622+
# v2: unified hierarchy (correct filenames)
1623+
################################################################################
1624+
class CgroupV2Info:
1625+
@staticmethod
1626+
def register(into: InfoGroup, extended: bool):
1627+
_, base = _v2_path()
1628+
# Primary = effective (v2 leaf cpus/mems can be empty otherwise)
1629+
into.addf("CPUs", pjoin(base, "cpuset.cpus.effective"), r"(.+)", tointlist)
1630+
into.addf("Mems", pjoin(base, "cpuset.mems.effective"), r"(.+)", tointlist)
1631+
into.required("CPUs", "Mems")
1632+
if extended:
1633+
# expose the same files under explicit names too (optional)
1634+
into.addf("CPUs.effective", pjoin(base, "cpuset.cpus.effective"), r"(.+)", tointlist)
1635+
into.addf("Mems.effective", pjoin(base, "cpuset.mems.effective"), r"(.+)", tointlist)
1636+
# no extra required() needed
1637+
1638+
1639+
################################################################################
1640+
# Public dispatcher: keeps external API the same
1641+
################################################################################
1642+
class CgroupInfo(InfoGroup):
1643+
def __init__(self, extended: bool = False, anonymous: bool = False):
1644+
super().__init__(name="Cgroups", extended=extended, anonymous=anonymous)
1645+
is_v2, _ = _v2_path()
1646+
if is_v2:
1647+
CgroupV2Info.register(self, extended)
1648+
else:
1649+
CgroupV1Info.register(self, extended)
15921650

15931651

15941652
################################################################################
@@ -3851,9 +3909,9 @@ def read_cli(cliargs):
38513909
# Check if compare file exists and readable
38523910
if pargs["compare"] is not None:
38533911
if not pexists(pargs["compare"]):
3854-
raise ValueError("State file '{}' does not exist".format(pargs["json"]))
3912+
raise ValueError("State file '{}' does not exist".format(pargs["compare"]))
38553913
if not os.access(pargs["compare"], os.R_OK):
3856-
raise ValueError("State file '{}' is not readable".format(pargs["json"]))
3914+
raise ValueError("State file '{}' is not readable".format(pargs["compare"]))
38573915
# Check if configuration file exists and is readable
38583916
if pargs["configfile"] is not None:
38593917
if not pexists(pargs["configfile"]):
@@ -4089,10 +4147,6 @@ def main():
40894147
print("[probe] OSError while probing:", e)
40904148
traceback.print_exc()
40914149
sys.exit(1)
4092-
# Generate subclasses of MachineState
4093-
mstate.generate()
4094-
# Update the current state
4095-
mstate.update()
40964150

40974151
# Compare current state to a saved file
40984152
if cliargs["compare"] is not None:

machinestate_pkg/CgroupInfo.py

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,79 @@
1-
from .common import InfoGroup, process_file, re, pjoin, tointlist
1+
from .common import InfoGroup, process_file, re, pjoin, tointlist
22

33
################################################################################
4-
# Infos about CGroups
4+
# Helper: detect cgroup v2 and compute base path
55
################################################################################
6-
class CgroupInfo(InfoGroup):
7-
def __init__(self, extended=False, anonymous=False):
8-
super(CgroupInfo, self).__init__(name="Cgroups", extended=extended, anonymous=anonymous)
6+
def _v2_path():
7+
"""
8+
Return (is_v2, base_path) for the current process.
9+
For v2, /proc/self/cgroup has a unified line like '0::/system.slice/...'.
10+
"""
11+
try:
12+
with open("/proc/self/cgroup", "r", encoding="utf-8", errors="ignore") as f:
13+
lines = f.read().splitlines()
14+
except Exception:
15+
return False, "/sys/fs/cgroup"
16+
17+
rel = "/"
18+
is_v2 = False
19+
for line in lines:
20+
parts = line.split(":", 2)
21+
# unified v2 line: controllers field empty, OR hierarchy id "0"
22+
if len(parts) == 3 and (parts[1] == "" or parts[0] == "0"):
23+
is_v2 = True
24+
rel = parts[2]
25+
break
26+
base = pjoin("/sys/fs/cgroup", rel.strip("/")) if rel else "/sys/fs/cgroup"
27+
return is_v2, base
28+
29+
30+
################################################################################
31+
# v1: keep your original logic (renamed to CgroupV1Info)
32+
################################################################################
33+
class CgroupV1Info:
34+
"""Registrar for cgroup v1 cpuset info (your original logic)."""
35+
@staticmethod
36+
def register(into: InfoGroup, extended: bool):
937
csetmat = re.compile(r"\d+\:cpuset\:([/\w\d\-\._]*)")
1038
cset = process_file(("/proc/self/cgroup", csetmat))
11-
if cset is not None:
12-
base = pjoin("/sys/fs/cgroup/cpuset", cset.strip("/"))
13-
self.addf("CPUs", pjoin(base, "cpuset.cpus"), r"(.+)", tointlist)
14-
self.addf("Mems", pjoin(base, "cpuset.mems"), r"(.+)", tointlist)
15-
self.required("CPUs", "Mems")
16-
if extended:
17-
names = ["CPUs.effective", "Mems.effective"]
18-
files = ["cpuset.effective_cpus", "cpuset.effective_mems"]
19-
for key, fname in zip(names, files):
20-
self.addf(key, pjoin(base, fname), r"(.+)", tointlist)
21-
self.required(key)
39+
if not cset:
40+
return
41+
base = pjoin("/sys/fs/cgroup/cpuset", cset.strip("/"))
42+
into.addf("CPUs", pjoin(base, "cpuset.cpus"), r"(.+)", tointlist)
43+
into.addf("Mems", pjoin(base, "cpuset.mems"), r"(.+)", tointlist)
44+
into.required("CPUs", "Mems")
45+
if extended:
46+
into.addf("CPUs.effective", pjoin(base, "cpuset.effective_cpus"), r"(.+)", tointlist)
47+
into.addf("Mems.effective", pjoin(base, "cpuset.effective_mems"), r"(.+)", tointlist)
48+
into.required("CPUs.effective", "Mems.effective")
49+
50+
51+
################################################################################
52+
# v2: unified hierarchy (correct filenames)
53+
################################################################################
54+
class CgroupV2Info:
55+
@staticmethod
56+
def register(into: InfoGroup, extended: bool):
57+
_, base = _v2_path()
58+
# Primary = effective (v2 leaf cpus/mems can be empty otherwise)
59+
into.addf("CPUs", pjoin(base, "cpuset.cpus.effective"), r"(.+)", tointlist)
60+
into.addf("Mems", pjoin(base, "cpuset.mems.effective"), r"(.+)", tointlist)
61+
into.required("CPUs", "Mems")
62+
if extended:
63+
# expose the same files under explicit names too (optional)
64+
into.addf("CPUs.effective", pjoin(base, "cpuset.cpus.effective"), r"(.+)", tointlist)
65+
into.addf("Mems.effective", pjoin(base, "cpuset.mems.effective"), r"(.+)", tointlist)
66+
# no extra required() needed
67+
68+
69+
################################################################################
70+
# Public dispatcher: keeps external API the same
71+
################################################################################
72+
class CgroupInfo(InfoGroup):
73+
def __init__(self, extended: bool = False, anonymous: bool = False):
74+
super().__init__(name="Cgroups", extended=extended, anonymous=anonymous)
75+
is_v2, _ = _v2_path()
76+
if is_v2:
77+
CgroupV2Info.register(self, extended)
78+
else:
79+
CgroupV1Info.register(self, extended)

machinestate_pkg/script.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ def read_cli(cliargs):
7777
# Check if compare file exists and readable
7878
if pargs["compare"] is not None:
7979
if not pexists(pargs["compare"]):
80-
raise ValueError("State file '{}' does not exist".format(pargs["json"]))
80+
raise ValueError("State file '{}' does not exist".format(pargs["compare"]))
8181
if not os.access(pargs["compare"], os.R_OK):
82-
raise ValueError("State file '{}' is not readable".format(pargs["json"]))
82+
raise ValueError("State file '{}' is not readable".format(pargs["compare"]))
8383
# Check if configuration file exists and is readable
8484
if pargs["configfile"] is not None:
8585
if not pexists(pargs["configfile"]):
@@ -315,10 +315,6 @@ def main():
315315
print("[probe] OSError while probing:", e)
316316
traceback.print_exc()
317317
sys.exit(1)
318-
# Generate subclasses of MachineState
319-
mstate.generate()
320-
# Update the current state
321-
mstate.update()
322318

323319
# Compare current state to a saved file
324320
if cliargs["compare"] is not None:

0 commit comments

Comments
 (0)