|
5 | 5 | import os |
6 | 6 | import re |
7 | 7 | import sys |
| 8 | +from collections import defaultdict |
8 | 9 | from contextlib import suppress |
9 | 10 | from functools import cached_property |
10 | 11 | from pathlib import Path |
|
17 | 18 |
|
18 | 19 | from devito.logger import warning |
19 | 20 | from devito.tools import all_equal, as_tuple, memoized_func |
| 21 | +from devito.warnings import warn |
20 | 22 |
|
21 | 23 | __all__ = [ # noqa: RUF022 |
22 | 24 | 'platform_registry', 'get_cpu_info', 'get_gpu_info', 'get_visible_devices', |
|
25 | 27 | 'Platform', 'Cpu64', 'Intel64', 'IntelSkylake', 'Amd', 'Arm', 'Power', |
26 | 28 | 'Device', |
27 | 29 | 'NvidiaDevice', 'AmdDevice', 'IntelDevice', |
| 30 | + 'old_get_cpu_info', |
28 | 31 | # Brand-agnostic |
29 | 32 | 'ANYCPU', 'ANYGPU', |
30 | 33 | # Intel CPUs |
|
48 | 51 |
|
49 | 52 |
|
50 | 53 | @memoized_func |
51 | | -def get_cpu_info(): |
| 54 | +def old_get_cpu_info(): |
52 | 55 | """Attempt CPU info autodetection.""" |
53 | 56 |
|
54 | 57 | # Obtain textual cpu info |
@@ -168,6 +171,166 @@ def get_cpu_brand(): |
168 | 171 | cpu_info['physical'] = physical |
169 | 172 | return cpu_info |
170 | 173 |
|
| 174 | +def text2dict(text): |
| 175 | + return { |
| 176 | + line.split(':', 1)[0].strip(): line.split(':', 1)[1].strip() |
| 177 | + for line in text.splitlines() |
| 178 | + } |
| 179 | + |
| 180 | +def cast2numeric(adict): |
| 181 | + # Try and convert numeric values |
| 182 | + for k, v in adict.items(): |
| 183 | + if not v: |
| 184 | + adict[k] = None |
| 185 | + continue |
| 186 | + for cast in [int, float]: |
| 187 | + with suppress(ValueError): |
| 188 | + adict[k] = cast(v) |
| 189 | + break |
| 190 | + return adict |
| 191 | + |
| 192 | +def set2range(aset): |
| 193 | + if len(aset) == 1: |
| 194 | + r = aset.pop() |
| 195 | + else: |
| 196 | + r = aset |
| 197 | + return r |
| 198 | + |
| 199 | +@memoized_func |
| 200 | +def proc_cpuinfo(): |
| 201 | + """ Creates a `dict` containing the information in `/proc/cpuinfo` |
| 202 | + """ |
| 203 | + # Obtain CPU info as text |
| 204 | + try: |
| 205 | + with open('/proc/cpuinfo') as f: |
| 206 | + lines = f.read() |
| 207 | + command = 'cat /proc/cpuinfo' |
| 208 | + except FileNotFoundError: |
| 209 | + lines = '' |
| 210 | + command = '`/proc/cpuinfo` not found' |
| 211 | + warn(f'File {command}') |
| 212 | + |
| 213 | + hwthreads = lines.strip().split('\n\n') |
| 214 | + logical = len(hwthreads) |
| 215 | + |
| 216 | + info = [] |
| 217 | + for hwt in hwthreads: |
| 218 | + info.append(cast2numeric(text2dict(hwt))) |
| 219 | + |
| 220 | + # Nightmare |
| 221 | + variations = defaultdict(set) |
| 222 | + for hwt in info: |
| 223 | + for k, v in hwt.items(): |
| 224 | + variations[k].add(v) |
| 225 | + |
| 226 | + final = {} |
| 227 | + for k, v in variations.items(): |
| 228 | + final[k] = set2range(v) |
| 229 | + |
| 230 | + # `cpu MHz` is a "live" value so ignore it |
| 231 | + with suppress(KeyError): |
| 232 | + del final['cpu MHz'] |
| 233 | + |
| 234 | + final['command'] = command |
| 235 | + |
| 236 | + return final |
| 237 | + |
| 238 | +@memoized_func |
| 239 | +def lscpu(): |
| 240 | + """ Creates a `dict` containing the information from `lscpu` |
| 241 | + """ |
| 242 | + # Use `lscpu -J` if available (not available prior to v2.30, cerca 2017) |
| 243 | + try: |
| 244 | + ret = run(['lscpu', '-J'], capture_output=True, text=True) |
| 245 | + info = { |
| 246 | + x['field'].rstrip(':'): x['data'] |
| 247 | + for x in json.loads(ret.stdout)['lscpu'] |
| 248 | + } |
| 249 | + info['command'] = 'lscpu -J' |
| 250 | + except Exception as e: |
| 251 | + info = None |
| 252 | + |
| 253 | + # Use `lscpu` if `-J` argument is not available |
| 254 | + if info is None: |
| 255 | + try: |
| 256 | + ret = run(['lscpu'], capture_output=True, text=True) |
| 257 | + info = text2dict(ret.stdout) |
| 258 | + info['command'] = 'lscpu' |
| 259 | + except Exception as e: |
| 260 | + msg = '`lscpu` not found' |
| 261 | + warn(f'Command {msg}') |
| 262 | + info = {'command': msg} |
| 263 | + |
| 264 | + return cast2numeric(info) |
| 265 | + |
| 266 | +@memoized_func |
| 267 | +def get_cpu_info(): |
| 268 | + """Collect CPU information |
| 269 | +
|
| 270 | + This function enriches the output of the `get_cpu_info` function provided by |
| 271 | + `cpuinfo` by adding the number of physical and logical cores. |
| 272 | +
|
| 273 | + There are numerous workarounds for different platforms where issues have |
| 274 | + been encountered previously. |
| 275 | +
|
| 276 | + Returns a dictionary with at least the following keys populated: |
| 277 | + - brand: str, fallback '' |
| 278 | + - flags: list[str], fallback [] |
| 279 | + - logical: int, fallback 1 |
| 280 | + - physical: int, fallback 1 |
| 281 | + """ |
| 282 | + try: |
| 283 | + cpu_info = cpuinfo.get_cpu_info() |
| 284 | + except Exception as e: |
| 285 | + warn(f'Calling `cpuinfo.get_cpu_info()` faised an exception\n {e !s}') |
| 286 | + cpu_info = {} |
| 287 | + |
| 288 | + cpu_info['logical'] = psutil.cpu_count(logical=True) |
| 289 | + cpu_info['physical'] = psutil.cpu_count(logical=False) |
| 290 | + |
| 291 | + # At this point on a well behaved system we have everything that we need. |
| 292 | + # If only life were that simple! We now check what we obtained |
| 293 | + |
| 294 | + procinfo = proc_cpuinfo() |
| 295 | + lscpuinfo = lscpu() |
| 296 | + # brand: |
| 297 | + if cpu_info.get('brand') is None: |
| 298 | + for source, key in ( |
| 299 | + (procinfo, 'model name'), |
| 300 | + (procinfo, 'cpu'), |
| 301 | + (cpuinfo, 'arch'), |
| 302 | + (cpuinfo, 'brand_raw'), |
| 303 | + ): |
| 304 | + cpu_info['brand'] = source.get(key) |
| 305 | + if cpu_info.get('brand'): |
| 306 | + break |
| 307 | + else: |
| 308 | + cpu_info['brand'] = '' |
| 309 | + |
| 310 | + # flags: |
| 311 | + if cpu_info.get('flags') is None: |
| 312 | + for source, key in ( |
| 313 | + (procinfo, 'features'), |
| 314 | + (procinfo, 'flags'), |
| 315 | + ): |
| 316 | + cpu_info['flags'] = source.get(key) |
| 317 | + if cpu_info.get('flags'): |
| 318 | + break |
| 319 | + else: |
| 320 | + cpu_info['flags'] = [] |
| 321 | + |
| 322 | + # logical |
| 323 | + if cpu_info.get('logical') is None: |
| 324 | + cpu_info['logical'] = lscpuinfo.get('CPU(s)', 1) |
| 325 | + |
| 326 | + # physical |
| 327 | + if cpu_info.get('physical') is None: |
| 328 | + cpu_info['physical'] = lscpuinfo.get('Core(s) per socket', 1) * lscpuinfo.get('Socket(s)', 1) |
| 329 | + |
| 330 | + cpu_info[procinfo.pop('command')] = procinfo |
| 331 | + cpu_info[lscpuinfo.pop('command')] = lscpuinfo |
| 332 | + |
| 333 | + return cpu_info |
171 | 334 |
|
172 | 335 | @memoized_func |
173 | 336 | def get_gpu_info(): |
@@ -695,7 +858,7 @@ def check_cuda_runtime(): |
695 | 858 |
|
696 | 859 |
|
697 | 860 | @memoized_func |
698 | | -def lscpu(): |
| 861 | +def old_lscpu(): |
699 | 862 | try: |
700 | 863 | p1 = Popen(['lscpu'], stdout=PIPE, stderr=PIPE) |
701 | 864 | except OSError: |
|
0 commit comments