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

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""" """ 

4 

5# NO_ruff: noqa: T201, T203 `print` found 

6 

7from __future__ import annotations 

8 

9import datetime 

10import platform 

11import socket 

12import sys 

13import uuid 

14from dataclasses import dataclass 

15from pathlib import Path 

16from typing import Any 

17 

18import psutil 

19from typing_extensions import Self 

20 

21from .util import get_machine_id, get_project_info, hash_string, sluggify 

22 

23 

24@dataclass 

25class HWInfo: 

26 cpu: str 

27 ram: str 

28 machine: str 

29 machine_id: str 

30 mac: str 

31 # gpu: str 

32 

33 def slug(self) -> str: 

34 return sluggify(f"{self.machine}_{self.ram}") 

35 

36 def to_dict(self) -> dict[str, Any]: 

37 return {"cpu": self.cpu, "ram": self.ram, "machine": self.machine} 

38 

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 

43 

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 ) 

52 

53 

54@dataclass 

55class ProjectInfo: 

56 name: str 

57 version: str 

58 root_folder: Path 

59 pyproject_toml: dict | None = None 

60 

61 def slug(self) -> str: 

62 return sluggify(f"v{self.version}") 

63 

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 } 

70 

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"] 

87 

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 

95 

96 

97@dataclass 

98class OSInfo: 

99 name: str 

100 version: str 

101 

102 def slug(self) -> str: 

103 return sluggify(f"{self.name}_{self.version}") 

104 

105 def to_dict(self) -> dict[str, Any]: 

106 return {"name": self.name, "version": self.version} 

107 

108 @property 

109 def is_windows(self) -> bool: 

110 return self.name == "Windows" 

111 

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) 

118 

119 

120@dataclass 

121class PythonInfo: 

122 version: str 

123 implementation: str 

124 compiler: str 

125 build: str 

126 debug_mode: bool 

127 optimized: bool 

128 

129 def slug(self) -> str: 

130 # return sluggify(f"Py{self.version}") 

131 return sluggify(f"{self.implementation}_{self.version}") 

132 

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}" 

143 

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 } 

151 

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 ) 

167 

168 

169# @singleton 

170class BaseContextInfo: 

171 """ 

172 Runtime context information about the client system (constant). 

173 """ 

174 

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) 

186 

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 ) 

196 

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) 

203 

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 ) 

217 

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 }