Skip to content

Commit

Permalink
new: Add initial support for reading cgroup information. (#41)
Browse files Browse the repository at this point in the history
This introduces a breaking change to generate_statistics.
Moving forward, generate_statistics will now generate 4 items.
The fourth group is CStat/CGroup information, similar to TStat.
  • Loading branch information
dfrtz authored Sep 21, 2024
1 parent 551b4df commit 5335380
Show file tree
Hide file tree
Showing 16 changed files with 366 additions and 65 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ import atoparser

with open(file, 'rb') as raw_file:
header = atoparser.get_header(raw_file)
for record, sstat, tstat in atoparser.generate_statistics(raw_file, header):
for record, sstat, tstats, cgroups in atoparser.generate_statistics(raw_file, header):
total_cycles = record.interval * sstat.cpu.nrcpu * header.hertz
usage = 1 - sstat.cpu.all.itime / total_cycles
print(f'CPU usage was {usage:.02%}')
Expand Down
3 changes: 3 additions & 0 deletions atoparser/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"""Libraries for reading Atop raw data files."""

from atoparser.utils import CGChainer
from atoparser.utils import CStat
from atoparser.utils import Header
from atoparser.utils import Record
from atoparser.utils import SStat
from atoparser.utils import TStat
from atoparser.utils import generate_statistics
from atoparser.utils import get_cstat
from atoparser.utils import get_header
from atoparser.utils import get_record
from atoparser.utils import get_sstat
Expand Down
17 changes: 14 additions & 3 deletions atoparser/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ def parse_args() -> argparse.Namespace:
action="store_true",
help="Include TStats/PStats in output. Very verbose.",
)
parser.add_argument(
"--cstats",
action="store_true",
help="Include CGroup/CStats in output. Only available with Atop 2.11+ logs. Verbose.",
)
args = parser.parse_args()
return args

Expand All @@ -70,10 +75,14 @@ def main() -> None:
)
continue
parsers = PARSEABLE_MAP.get(header.semantic_version, PARSEABLE_MAP["1.26"])
for record, sstat, tstat in atoparser.generate_statistics(raw_file, header, raise_on_truncation=False):
for record, sstat, tstats, cgroups in atoparser.generate_statistics(
raw_file,
header,
raise_on_truncation=False,
):
if args.parseables:
for parseable in args.parseables:
for sample in parsers[parseable](header, record, sstat, tstat):
for sample in parsers[parseable](header, record, sstat, tstats):
sample["parseable"] = parseable
samples.append(sample)
else:
Expand All @@ -83,7 +92,9 @@ def main() -> None:
"sstat": atoparser.struct_to_dict(sstat),
}
if args.tstats:
converted["tstat"] = [atoparser.struct_to_dict(stat) for stat in tstat]
converted["tstat"] = [atoparser.struct_to_dict(stat) for stat in tstats]
if args.cstats:
converted["cgroup"] = [atoparser.struct_to_dict(stat) for stat in cgroups]
samples.append(converted)
print(json.dumps(samples, indent=2 if args.pretty_print else None))

Expand Down
6 changes: 5 additions & 1 deletion atoparser/structs/atop_1_26.py
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ class PStat(ctypes.Structure):
]


# Add TStat aliases for forwards compatibility. TStat and PStat are the same base stats, but renamed in later versions.
# Add TStat alias for forwards compatibility. TStat and PStat are the same base stats, but renamed in later versions.
TStat = PStat


Expand Down Expand Up @@ -671,3 +671,7 @@ class Header(ctypes.Structure, HeaderMixin):
Record = Record
SStat = SStat
PStat = PStat
# Add TStat alias for forwards compatibility. TStat and PStat are the same base stats, but renamed in later versions.
TStat = PStat
CStat = None
CGChainer = None
2 changes: 2 additions & 0 deletions atoparser/structs/atop_2_10.py
Original file line number Diff line number Diff line change
Expand Up @@ -458,3 +458,5 @@ class Header(atop_2_8.Header):
Record = Record
SStat = SStat
TStat = TStat
CStat = None
CGChainer = None
144 changes: 144 additions & 0 deletions atoparser/structs/atop_2_11.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from atoparser.structs.shared import HeaderMixin
from atoparser.structs.shared import UTSName
from atoparser.structs.shared import count_t
from atoparser.structs.shared import pid_t
from atoparser.structs.shared import time_t

# Disable the following pylint warnings to allow the variables and classes to match the style from the C.
Expand Down Expand Up @@ -526,6 +527,147 @@ class TStat(ctypes.Structure):
]


class CGGen(ctypes.Structure):
"""Embedded struct to describe a single cgroup's general information.
C Name: cggen
C Location: cgroups.h
C Parent: cstat
"""

_fields_ = [
("structlen", ctypes.c_int),
("sequence", ctypes.c_int),
("parentseq", ctypes.c_int),
("depth", ctypes.c_int),
("nprocs", ctypes.c_int),
("procsbelow", ctypes.c_int),
("namelen", ctypes.c_int),
("fullnamelen", ctypes.c_int),
("ifuture", ctypes.c_int * 4),
("namehash", ctypes.c_long),
("lfuture", ctypes.c_long * 4),
]


class CGConf(ctypes.Structure):
"""Embedded struct to describe a single cgroup's configuration.
C Name: cgconfg
C Location: cgroups.h
C Parent: cstat
"""

_fields_ = [
("cpuweight", ctypes.c_int),
("cpumax", ctypes.c_int),
("memmax", count_t),
("swpmax", count_t),
("dskweight", ctypes.c_int),
("ifuture", ctypes.c_int * 5),
("cfuture", count_t * 5),
]


class CGCPU(ctypes.Structure):
"""Embedded struct to describe a single cgroup's processor usage.
C Name: cgcpu
C Location: cgroups.h
C Parent: cstat
"""

_fields_ = [
("utime", count_t),
("stime", count_t),
("somepres", count_t),
("fullpres", count_t),
("cfuture", count_t * 5),
]


class CGMem(ctypes.Structure):
"""Embedded struct to describe a single cgroup's memory usage.
C Name: cgmem
C Location: cgroups.h
C Parent: cstat
"""

_fields_ = [
("current", count_t),
("anon", count_t),
("file", count_t),
("kernel", count_t),
("shmem", count_t),
("somepres", count_t),
("fullpres", count_t),
("cfuture", count_t * 5),
]


class CGDSK(ctypes.Structure):
"""Embedded struct to describe a single cgroup's disk usage.
C Name: cgdsk
C Location: cgroups.h
C Parent: cstat
"""

_fields_ = [
("rbytes", count_t),
("wbytes", count_t),
("rios", count_t),
("wios", count_t),
("somepres", count_t),
("fullpres", count_t),
("cfuture", count_t * 5),
]


class CStat(ctypes.Structure):
"""Top level struct to describe general info and metrics per cgroup.
C Name: cstat
C Location: cgroups.h
"""

_fields_ = [
("gen", CGGen),
("conf", CGConf),
("cpu", CGCPU),
("mem", CGMem),
("dsk", CGDSK),
("cgname", ctypes.c_char * 0), # Variable length character array. N.B. Currently unsupported.
]


class CGChainer:
"""Structure used to track a pidlist with a cgroup.
Due to the variable length of cgchainer values, this is a custom C struct like object, rather than a true C struct.
It provides the same basic attributes as a C struct to allow conversion to JSON.
C Name: cgchainer
C Location: cgroups.h
"""

_fields_ = [
("cstat", CStat),
("proclist", ctypes.Array[pid_t]),
]

def __init__(self, cstat: CStat, proclist: ctypes.Array[pid_t]) -> None:
"""Initialize the starting CGroup chain values.
Args:
cstat: The primary cstat information applicable to the processes.
proclist: Process IDs contained in the cgroup.
"""
self.cstat = cstat
self.proclist = proclist


class Header(ctypes.Structure, HeaderMixin):
"""Top level struct to describe information about the system running Atop and the log file itself.
Expand Down Expand Up @@ -559,3 +701,5 @@ class Header(ctypes.Structure, HeaderMixin):
Record = Record
SStat = SStat
TStat = TStat
CStat = CStat
CGChainer = CGChainer
2 changes: 2 additions & 0 deletions atoparser/structs/atop_2_3.py
Original file line number Diff line number Diff line change
Expand Up @@ -579,3 +579,5 @@ class Header(ctypes.Structure, HeaderMixin):
Record = Record
SStat = SStat
TStat = TStat
CStat = None
CGChainer = None
2 changes: 2 additions & 0 deletions atoparser/structs/atop_2_4.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,5 @@ class Header(atop_2_3.Header):
Record = Record
SStat = SStat
TStat = TStat
CStat = None
CGChainer = None
2 changes: 2 additions & 0 deletions atoparser/structs/atop_2_5.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,5 @@ class Header(atop_2_3.Header):
Record = Record
SStat = SStat
TStat = TStat
CStat = None
CGChainer = None
2 changes: 2 additions & 0 deletions atoparser/structs/atop_2_6.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,5 @@ class Header(atop_2_3.Header):
Record = Record
SStat = SStat
TStat = TStat
CStat = None
CGChainer = None
2 changes: 2 additions & 0 deletions atoparser/structs/atop_2_7.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,3 +399,5 @@ class Header(atop_2_3.Header):
Record = Record
SStat = SStat
TStat = TStat
CStat = None
CGChainer = None
2 changes: 2 additions & 0 deletions atoparser/structs/atop_2_8.py
Original file line number Diff line number Diff line change
Expand Up @@ -612,3 +612,5 @@ class Header(ctypes.Structure, HeaderMixin):
Record = Record
SStat = SStat
TStat = TStat
CStat = None
CGChainer = None
2 changes: 2 additions & 0 deletions atoparser/structs/atop_2_9.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,5 @@ class Header(atop_2_8.Header):
Record = Record
SStat = SStat
TStat = TStat
CStat = None
CGChainer = None
12 changes: 9 additions & 3 deletions atoparser/structs/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
count_t = ctypes.c_longlong

# Definitions from sys/types.h
c_int32_t = ctypes.c_int32
off_t = ctypes.c_long
pid_t = ctypes.c_int

Expand All @@ -32,7 +31,12 @@ class HeaderMixin:
supported_version: The version of Atop that this header is compatible with as <major.<minor>.
"""

supported_version = None
supported_version: str
Record: ctypes.Structure
SStat: ctypes.Structure
TStat: ctypes.Structure
CStat: ctypes.Structure
CGChainer: ctypes.Structure

def check_compatibility(self) -> None:
"""Verify if the loaded values are compatible with this header version.
Expand All @@ -48,7 +52,9 @@ def check_compatibility(self) -> None:
if self.major_version >= 2 and self.minor_version >= 3:
sizes.append(("TStat", self.tstatlen, ctypes.sizeof(self.TStat)))
else:
sizes.append(("PStat", self.pstatlen, ctypes.sizeof(self.PStat)))
sizes.append(("PStat", self.pstatlen, ctypes.sizeof(self.TStat)))
if self.major_version >= 2 and self.minor_version >= 11:
sizes.append(("CStat", self.cstatlen, ctypes.sizeof(self.CStat)))
if any(size[1] != size[2] for size in sizes):
raise ValueError(
f"File has incompatible Atop format. Struct length evaluations (type, found, expected): {sizes}"
Expand Down
Loading

0 comments on commit 5335380

Please sign in to comment.