diff --git a/archey/entries/cpu.py b/archey/entries/cpu.py index ba7196d..ccec929 100644 --- a/archey/entries/cpu.py +++ b/archey/entries/cpu.py @@ -39,6 +39,14 @@ class CPU(Entry): r"^Socket\(s\)\s*:\s*(\d+)$", flags=re.IGNORECASE | re.MULTILINE, ) + _CLUSTERS_REGEXP = re.compile( + r"^Cluster\(s\)\s*:\s*(\d+)$", + flags=re.IGNORECASE | re.MULTILINE, + ) + _CORES_PER_CLUSTER_REGEXP = re.compile( + r"^Core\(s\) per cluster\s*:\s*(\d+)$", + flags=re.IGNORECASE | re.MULTILINE, + ) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -52,9 +60,9 @@ class CPU(Entry): self.value = self._parse_system_profiler() or self._parse_sysctl_machdep() if not self.value: - # This test case has been built for some ARM architectures (see #29). - # Sometimes, `model name` info is not present within `/proc/cpuinfo`. - # We use the output of `lscpu` program (util-linux-ng) to retrieve it. + # This test case has been built for some ARM architectures (see #29 and #127). + # Sometimes, model name and physical id info are missing from `/proc/cpuinfo`. + # We use the output of `lscpu` program (util-linux-ng) to properly detect logical cores. self.value = self._parse_lscpu_output() @classmethod @@ -95,16 +103,16 @@ class CPU(Entry): return [] nb_threads = cls._THREADS_PER_CORE_REGEXP.findall(cpu_info) - nb_cores = cls._CORES_PER_SOCKET_REGEXP.findall(cpu_info) - nb_sockets = cls._SOCKETS_REGEXP.findall(cpu_info) + nb_cores = cls._CORES_PER_SOCKET_REGEXP.findall( + cpu_info + ) or cls._CORES_PER_CLUSTER_REGEXP.findall(cpu_info) + nb_slots = cls._SOCKETS_REGEXP.findall(cpu_info) or cls._CLUSTERS_REGEXP.findall(cpu_info) model_names = cls._MODEL_NAME_REGEXP.findall(cpu_info) cpus_list = [] - for threads, cores, sockets, model_name in zip( - nb_threads, nb_cores, nb_sockets, model_names - ): - for _ in range(int(sockets)): + for threads, cores, slots, model_name in zip(nb_threads, nb_cores, nb_slots, model_names): + for _ in range(int(slots)): # Sometimes CPU model names contain extra ugly white-spaces. cpus_list.append({re.sub(r"\s+", " ", model_name): int(threads) * int(cores)}) diff --git a/archey/test/entries/test_archey_cpu.py b/archey/test/entries/test_archey_cpu.py index df77b1a..ea190ad 100644 --- a/archey/test/entries/test_archey_cpu.py +++ b/archey/test/entries/test_archey_cpu.py @@ -278,6 +278,20 @@ Vendor ID: CPU-VENDOR-NAME CPU family: Z Model: \xde\xad\xbe\xef Model name: CPU-MODEL-NAME +""", + """\ +Architecture: aarch64 +CPU op-mode(s): 32-bit, 64-bit +Byte Order: Little Endian +CPU(s): 4 +On-line CPU(s) list: 0-3 +Vendor ID: ARM +Model name: Cortex-A72 +Model: 3 +Thread(s) per core: 1 +Core(s) per cluster: 4 +Socket(s): - +Cluster(s): 1 """, ], ) @@ -285,8 +299,8 @@ Model name: CPU-MODEL-NAME """ Test model name parsing from `lscpu` output. - See issue #29 (ARM architectures). - `/proc/cpuinfo` will not contain `model name` info. + See issues #29 and #127 (ARM architectures). + `/proc/cpuinfo` will not contain `model name` (nor `physical id`) info. `lscpu` output will be used instead. """ with self.subTest("Simple unique CPU."): @@ -307,6 +321,12 @@ Model name: CPU-MODEL-NAME [{"CPU-MODEL-NAME": 8}, {"CPU-MODEL-NAME": 8}], ) + with self.subTest("4 CPUs, 1 cluster."): + self.assertListEqual( + CPU._parse_lscpu_output(), # pylint: disable=protected-access + [{"Cortex-A72": 4}], + ) + @HelperMethods.patch_clean_configuration def test_various_output_configuration(self): """Test `output` overloading based on user preferences combination"""