Skip to content

Commit

Permalink
new: Add support for Atop 2.4.
Browse files Browse the repository at this point in the history
  • Loading branch information
dfrtz authored Jan 14, 2024
1 parent 3b02204 commit 5098a3d
Show file tree
Hide file tree
Showing 9 changed files with 517 additions and 54 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ For full information on the amazing performance monitoring software that creates
## Compatibility

- Supports Python 3.10+
- Supports Atop 1.26 and 2.3.0.
- Supports Atop 1.26 and 2.3.0 through 2.4.0.


## Getting Started
Expand Down
11 changes: 10 additions & 1 deletion pyatop/atop_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,39 +30,48 @@

from pyatop.structs import atop_1_26
from pyatop.structs import atop_2_3
from pyatop.structs import atop_2_4

Header = Union[
atop_1_26.Header,
atop_2_3.Header,
atop_2_4.Header,
]
Record = Union[
atop_1_26.Record,
atop_2_3.Record,
atop_2_4.Record,
]
SStat = Union[
atop_1_26.SStat,
atop_2_3.SStat,
atop_2_4.SStat,
]
TStat = Union[ # pylint: disable=invalid-name
atop_1_26.TStat,
atop_2_3.TStat,
atop_2_4.TStat,
]

_HEADER_BY_VERSION: dict[str, type[Header]] = {
"1.26": atop_1_26.Header,
"2.3": atop_2_3.Header,
"2.4": atop_2_4.Header,
}
_RECORD_BY_VERSION: dict[str, type[Record]] = {
"1.26": atop_1_26.Record,
"2.3": atop_2_3.Record,
"2.4": atop_2_4.Record,
}
_SSTAT_BY_VERSION: dict[str, type[SStat]] = {
"1.26": atop_1_26.SStat,
"2.3": atop_2_3.SStat,
"2.4": atop_2_4.SStat,
}
_TSTAT_BY_VERSION: dict[str, type[TStat]] = {
"1.26": atop_1_26.TStat,
"2.3": atop_2_3.TStat,
"2.4": atop_2_4.TStat,
}
# Fallback to latest if there is no custom class provided to attempt backwards compatibility.
_DEFAULT_VERSION = list(_HEADER_BY_VERSION.keys())[-1]
Expand Down Expand Up @@ -265,7 +274,7 @@ def struct_to_dict(struct: ctypes.Structure) -> dict:
for subdata in field_data[: getattr(struct, struct.fields_limiters.get(field_name))]:
struct_dict[field_name].append(struct_to_dict(subdata))
elif isinstance(field_data, bytes):
struct_dict[field_name] = field_data.decode()
struct_dict[field_name] = field_data.decode(errors="ignore")
else:
struct_dict[field_name] = field_data
return struct_dict
2 changes: 2 additions & 0 deletions pyatop/atop_reader.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#! /usr/bin/env python3

"""Simple ATOP log processor."""

import argparse
Expand Down
36 changes: 10 additions & 26 deletions pyatop/structs/atop_1_26.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import ctypes

from pyatop.structs.shared import HeaderMixin
from pyatop.structs.shared import UTSName
from pyatop.structs.shared import count_t
from pyatop.structs.shared import time_t
Expand Down Expand Up @@ -41,7 +42,7 @@
MAXINTF = 32


class Header(ctypes.Structure):
class Header(ctypes.Structure, HeaderMixin):
"""Top level struct to describe information about the system running ATOP and the log file itself.
Field descriptions from atop:
Expand Down Expand Up @@ -87,31 +88,14 @@ def check_compatibility(self) -> None:
Raises:
ValueError if not compatible.
"""
compatible = [
self.rawheadlen == ctypes.sizeof(Header),
self.rawreclen == ctypes.sizeof(Record),
self.sstatlen == ctypes.sizeof(SStat),
self.pstatlen == ctypes.sizeof(PStat),
sizes = [
(self.rawheadlen, ctypes.sizeof(Header)),
(self.rawreclen, ctypes.sizeof(Record)),
(self.sstatlen, ctypes.sizeof(SStat)),
(self.pstatlen, ctypes.sizeof(PStat)),
]
if not all(compatible):
raise ValueError(f"File has incompatible atop format. Struct length evaluations: {compatible}")

@property
def semantic_version(self) -> str:
"""Convert the raw version into a semantic version.
Returns:
The final major.minor version from the header aversion.
Atop releases have "maintenance" versions, but they do not impact the header or file structure.
i.e., 1.26.1 is the same as 1.26.
"""
# Use a general getattr() call to ensure the instance can always set the attribute even on first call.
# C structs have various ways of creating instances, so __init__ is not always called to set up attributes.
if not getattr(self, "_version", None):
major = (self.aversion >> 8) & 0x7F
minor = self.aversion & 0xFF
self._version = f"{major}.{minor}" # pylint: disable=attribute-defined-outside-init
return self._version
if any(size[0] != size[1] for size in sizes):
raise ValueError(f"File has incompatible Atop format. Struct length evaluations (found, expected): {sizes}")


class Record(ctypes.Structure):
Expand Down Expand Up @@ -709,7 +693,7 @@ class NET(ctypes.Structure):


class PStat(ctypes.Structure):
"""Top level struct to describe multiple statistics categories per process as 'parseables'.
"""Top level struct to describe multiple statistic categories per process.
C Name: pstat
C Location: photoproc.h
Expand Down
36 changes: 10 additions & 26 deletions pyatop/structs/atop_2_3.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import ctypes

from pyatop.structs import atop_1_26
from pyatop.structs.shared import HeaderMixin
from pyatop.structs.shared import UTSName
from pyatop.structs.shared import count_t
from pyatop.structs.shared import time_t
Expand Down Expand Up @@ -46,7 +47,7 @@
MAXDKNAM = 32


class Header(ctypes.Structure):
class Header(ctypes.Structure, HeaderMixin):
"""Top level struct to describe information about the system running ATOP and the log file itself.
Field descriptions from atop:
Expand Down Expand Up @@ -92,31 +93,14 @@ def check_compatibility(self) -> None:
Raises:
ValueError if not compatible.
"""
compatible = [
self.rawheadlen == ctypes.sizeof(Header),
self.rawreclen == ctypes.sizeof(Record),
self.sstatlen == ctypes.sizeof(SStat),
self.tstatlen == ctypes.sizeof(TStat),
sizes = [
(self.rawheadlen, ctypes.sizeof(Header)),
(self.rawreclen, ctypes.sizeof(Record)),
(self.sstatlen, ctypes.sizeof(SStat)),
(self.tstatlen, ctypes.sizeof(TStat)),
]
if not all(compatible):
raise ValueError(f"File has incompatible atop format. Struct length evaluations: {compatible}")

@property
def semantic_version(self) -> str:
"""Convert the raw version into a semantic version.
Returns:
The final major.minor version from the header aversion.
Atop releases have "maintenance" versions, but they do not impact the header or file structure.
i.e., 2.3.1 is the same as 2.3.
"""
# Use a general getattr() call to ensure the instance can always set the attribute even on first call.
# C structs have various ways of creating instances, so __init__ is not always called to set up attributes.
if not getattr(self, "_version", None):
major = (self.aversion >> 8) & 0x7F
minor = self.aversion & 0xFF
self._version = f"{major}.{minor}" # pylint: disable=attribute-defined-outside-init
return self._version
if any(size[0] != size[1] for size in sizes):
raise ValueError(f"File has incompatible Atop format. Struct length evaluations (found, expected): {sizes}")


class Record(ctypes.Structure):
Expand Down Expand Up @@ -624,7 +608,7 @@ class NET(ctypes.Structure):


class TStat(ctypes.Structure):
"""Top level struct to describe multiple statistics categories per task/process.
"""Top level struct to describe multiple statistic categories per task/process.
C Name: tstat
C Location: photoproc.h
Expand Down
Loading

0 comments on commit 5098a3d

Please sign in to comment.