Coverage for /Users/martin/prj/git/benchman_pre/src/benchman/context_info.py: 80%
113 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-12-24 08:16 +0100
« prev ^ index » next coverage.py v7.6.4, created at 2024-12-24 08:16 +0100
1# (c) 2024 Martin Wendt; see https://github.com/mar10/benchman
2# Licensed under the MIT license: https://www.opensource.org/licenses/mit-license.php
3""" """
5# NO_ruff: noqa: T201, T203 `print` found
7from __future__ import annotations
9import datetime
10import platform
11import socket
12import sys
13import uuid
14from dataclasses import dataclass
15from pathlib import Path
16from typing import Any
18import psutil
19from typing_extensions import Self
21from .util import get_machine_id, get_project_info, hash_string, sluggify
24@dataclass
25class HWInfo:
26 cpu: str
27 ram: str
28 machine: str
29 machine_id: str
30 mac: str
31 # gpu: str
33 def slug(self) -> str:
34 return sluggify(f"{self.machine}_{self.ram}")
36 def to_dict(self) -> dict[str, Any]:
37 return {"cpu": self.cpu, "ram": self.ram, "machine": self.machine}
39 @classmethod
40 def create(cls) -> Self:
41 ram_info = f"{round(psutil.virtual_memory().total / (1024 ** 3))} GB"
42 # gpu_info = None # This would require a library like GPUtil to get dynamically
44 return cls(
45 cpu=platform.processor(),
46 ram=ram_info,
47 machine=platform.machine(),
48 machine_id=get_machine_id(),
49 mac=str(uuid.getnode()),
50 # gpu=gpu_info,
51 )
54@dataclass
55class ProjectInfo:
56 name: str
57 version: str
58 root_folder: Path
59 pyproject_toml: dict | None = None
61 def slug(self) -> str:
62 return sluggify(f"v{self.version}")
64 def to_dict(self) -> dict[str, Any]:
65 return {
66 "name": self.name,
67 "version": self.version,
68 "root_folder": str(self.root_folder),
69 }
71 @classmethod
72 def create(
73 cls,
74 *,
75 path: Path | str | None = None,
76 ) -> Self:
77 # Check if we are running from a project folder
78 if path is None:
79 path = Path.cwd()
80 else:
81 path = Path(path)
82 info = get_project_info(path)
83 project_root = info["project_root"]
84 project_name = info["project_name"]
85 project_version = info["project_version"]
86 pyproject_toml = info["pyproject_toml"]
88 project_info = cls(
89 root_folder=project_root,
90 name=project_name,
91 version=project_version,
92 )
93 project_info.pyproject_toml = pyproject_toml
94 return project_info
97@dataclass
98class OSInfo:
99 name: str
100 version: str
102 def slug(self) -> str:
103 return sluggify(f"{self.name}_{self.version}")
105 def to_dict(self) -> dict[str, Any]:
106 return {"name": self.name, "version": self.version}
108 @property
109 def is_windows(self) -> bool:
110 return self.name == "Windows"
112 @classmethod
113 def create(cls) -> Self:
114 uname = platform.uname()
115 name = platform.system()
116 version = uname.release # latform.version()
117 return cls(name=name, version=version)
120@dataclass
121class PythonInfo:
122 version: str
123 implementation: str
124 compiler: str
125 build: str
126 debug_mode: bool
127 optimized: bool
129 def slug(self) -> str:
130 # return sluggify(f"Py{self.version}")
131 return sluggify(f"{self.implementation}_{self.version}")
133 def implementation_version(self, *, strip_patch=False) -> str:
134 v = self.version
135 impl = self.implementation
136 if impl == "CPython": # 'CPython 3.9.7' -> 'Py39'
137 if strip_patch:
138 v = "".join(v.split(".")[:2])
139 impl = "Py"
140 else:
141 impl += " "
142 return f"{impl}{v}"
144 def to_dict(self) -> dict[str, Any]:
145 return {
146 "version": self.version,
147 "implementation": self.implementation,
148 "compiler": self.compiler,
149 "build": self.build,
150 }
152 @classmethod
153 def create(cls) -> PythonInfo:
154 version = platform.python_version()
155 implementation = platform.python_implementation()
156 compiler = platform.python_compiler()
157 build = platform.python_build()[0]
158 debug_mode = bool(getattr(sys, "gettrace", None) and sys.gettrace())
159 return cls(
160 version=version,
161 implementation=implementation,
162 compiler=compiler,
163 build=build,
164 debug_mode=debug_mode,
165 optimized=not sys.flags.debug,
166 )
169# @singleton
170class BaseContextInfo:
171 """
172 Runtime context information about the client system (constant).
173 """
175 def __init__(
176 self,
177 *,
178 path: Path | str | None = None,
179 ) -> None:
180 self.hostname = socket.gethostname()
181 self.date = datetime.datetime.now(datetime.timezone.utc)
182 self.python: PythonInfo = PythonInfo.create()
183 self.os: OSInfo = OSInfo.create()
184 self.hw: HWInfo = HWInfo.create()
185 self.project: ProjectInfo = ProjectInfo.create(path=path)
187 def __repr__(self):
188 uname = platform.uname()
189 return "{}<node: {!r}, os: {} {}, machine: {}>".format( # noqa: UP032
190 self.__class__.__name__,
191 uname.node,
192 uname.system,
193 uname.release,
194 uname.machine,
195 )
197 def client_slug(self) -> str:
198 """Identifies the current client system."""
199 # return hash_string(self.hw.mac, length=8)
200 # return self.hw.mac
201 # return hash_string(platform.node() + "_" + str(platform.uname()))
202 return hash_string(self.hw.machine_id)
204 def slug(self) -> str:
205 return sluggify(
206 "~".join(
207 [
208 self.project.slug(),
209 self.python.slug(),
210 self.date.strftime("%Y%m%d"),
211 self.hostname,
212 self.hw.slug(),
213 self.os.slug(),
214 ]
215 )
216 )
218 def to_dict(self) -> dict[str, Any]:
219 return {
220 "slug": self.slug(),
221 "date": self.date.isoformat(),
222 "hostname": self.hostname,
223 "python": self.python.to_dict(),
224 "os": self.os.to_dict(),
225 "hw": self.hw.to_dict(),
226 "project": self.project.to_dict(),
227 }