diff mbox series

cpupower: Implement CPU physical core querying

Message ID 20250305210901.24177-1-jwyatt@redhat.com
State New
Headers show
Series cpupower: Implement CPU physical core querying | expand

Commit Message

John B. Wyatt IV March 5, 2025, 9:08 p.m. UTC
This patch is also an issue report. get_cpu_topology will always save
into cpupower_topology a cores size of 0. The code to handle this looks
like it was commented out, and what is commented out is missing a curly
bracket.

https://elixir.bootlin.com/linux/v6.13.5/source/tools/power/cpupower/lib/cpupower.c#L206-L212

Inspiration was taken from psutil to implement this by querying
core_cpu_list. Instead of using a hashmap, I used a sorted array, and
counted the number of valid unique strings. The counting of this takes
place before the qsort for .pkg as the following code says it is
dependent on the order of that sort.

The previous code claimed Intel CPUs are not numbered correctly. I was
not able to reproduce that issue and removed that comment and the code.

This commit was tested with the libcpupower SWIG Python bindings and
performed correctly on 4 different setups. The most notable is the
Framework Intel laptop; a hybrid system of 4 P cores (8 threads) and 8 E
cores (8 threads).

The 4 setups: A 4 core virt-manager VM running Fedora 41 4c/4t (specs not
listed) was tested as a sanity test for VMs. A Lenovo Ryzen 7 Pro 7840HS
8c/16t. A Supermico Intel(R) Xeon(R) Gold 6330 CPU w/ 56c/112t with 2 CPU
sockets. A Framework 12th Gen Intel(R) Core(TM) i5-1240P with hybrid
cores.

CPU(s):                   16
  On-line CPU(s) list:    0-15
Vendor ID:                AuthenticAMD
  Model name:             AMD Ryzen 7 PRO 7840HS w/ Radeon 780M Graphics
    CPU family:           25
    Model:                116
    Thread(s) per core:   2
    Core(s) per socket:   8
    Socket(s):            1
    Stepping:             1

CPU(s):                   112
  On-line CPU(s) list:    0-111
Vendor ID:                GenuineIntel
  BIOS Vendor ID:         Intel(R) Corporation
  Model name:             Intel(R) Xeon(R) Gold 6330 CPU @ 2.00GHz
    BIOS Model name:      Intel(R) Xeon(R) Gold 6330 CPU @ 2.00GHz  CPU @ 2.0GHz
    BIOS CPU family:      179
    CPU family:           6
    Model:                106
    Thread(s) per core:   2
    Core(s) per socket:   28
    Socket(s):            2
    Stepping:             6

CPU(s):                   16
  On-line CPU(s) list:    0-15
Vendor ID:                GenuineIntel
  Model name:             12th Gen Intel(R) Core(TM) i5-1240P
    CPU family:           6
    Model:                154
    Thread(s) per core:   2
    Core(s) per socket:   12
    Socket(s):            1
    Stepping:             3

Signed-off-by: "John B. Wyatt IV" <jwyatt@redhat.com>
Signed-off-by: "John B. Wyatt IV" <sageofredondo@gmail.com>
---
 tools/power/cpupower/lib/cpupower.c | 48 ++++++++++++++++++++++++-----
 tools/power/cpupower/lib/cpupower.h |  3 ++
 2 files changed, 43 insertions(+), 8 deletions(-)

Comments

Shuah Khan March 6, 2025, 8:33 p.m. UTC | #1
On 3/5/25 14:08, John B. Wyatt IV wrote:
> This patch is also an issue report. get_cpu_topology will always save
> into cpupower_topology a cores size of 0. The code to handle this looks
> like it was commented out, and what is commented out is missing a curly
> bracket.
> 
> https://elixir.bootlin.com/linux/v6.13.5/source/tools/power/cpupower/lib/cpupower.c#L206-L212
> 
> Inspiration was taken from psutil to implement this by querying
> core_cpu_list. Instead of using a hashmap, I used a sorted array, and
> counted the number of valid unique strings. The counting of this takes
> place before the qsort for .pkg as the following code says it is
> dependent on the order of that sort.
> 
> The previous code claimed Intel CPUs are not numbered correctly. I was
> not able to reproduce that issue and removed that comment and the code.
> 
> This commit was tested with the libcpupower SWIG Python bindings and
> performed correctly on 4 different setups. The most notable is the
> Framework Intel laptop; a hybrid system of 4 P cores (8 threads) and 8 E
> cores (8 threads).
> 
> The 4 setups: A 4 core virt-manager VM running Fedora 41 4c/4t (specs not
> listed) was tested as a sanity test for VMs. A Lenovo Ryzen 7 Pro 7840HS
> 8c/16t. A Supermico Intel(R) Xeon(R) Gold 6330 CPU w/ 56c/112t with 2 CPU
> sockets. A Framework 12th Gen Intel(R) Core(TM) i5-1240P with hybrid
> cores.
> 
> CPU(s):                   16
>    On-line CPU(s) list:    0-15
> Vendor ID:                AuthenticAMD
>    Model name:             AMD Ryzen 7 PRO 7840HS w/ Radeon 780M Graphics
>      CPU family:           25
>      Model:                116
>      Thread(s) per core:   2
>      Core(s) per socket:   8
>      Socket(s):            1
>      Stepping:             1
> 
> CPU(s):                   112
>    On-line CPU(s) list:    0-111
> Vendor ID:                GenuineIntel
>    BIOS Vendor ID:         Intel(R) Corporation
>    Model name:             Intel(R) Xeon(R) Gold 6330 CPU @ 2.00GHz
>      BIOS Model name:      Intel(R) Xeon(R) Gold 6330 CPU @ 2.00GHz  CPU @ 2.0GHz
>      BIOS CPU family:      179
>      CPU family:           6
>      Model:                106
>      Thread(s) per core:   2
>      Core(s) per socket:   28
>      Socket(s):            2
>      Stepping:             6
> 
> CPU(s):                   16
>    On-line CPU(s) list:    0-15
> Vendor ID:                GenuineIntel
>    Model name:             12th Gen Intel(R) Core(TM) i5-1240P
>      CPU family:           6
>      Model:                154
>      Thread(s) per core:   2
>      Core(s) per socket:   12
>      Socket(s):            1
>      Stepping:             3
> 
> Signed-off-by: "John B. Wyatt IV" <jwyatt@redhat.com>
> Signed-off-by: "John B. Wyatt IV" <sageofredondo@gmail.com>
> ---

Thanks. Applied and will include in my PR for 6.15-rc1 to Rafael

https://web.git.kernel.org/pub/scm/linux/kernel/git/shuah/linux.git/log/?h=cpupower

thanks,
-- Shuah
diff mbox series

Patch

diff --git a/tools/power/cpupower/lib/cpupower.c b/tools/power/cpupower/lib/cpupower.c
index 7a2ef691b20e..ce8dfb8e46ab 100644
--- a/tools/power/cpupower/lib/cpupower.c
+++ b/tools/power/cpupower/lib/cpupower.c
@@ -10,6 +10,7 @@ 
 #include <stdio.h>
 #include <errno.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include "cpupower.h"
 #include "cpupower_intern.h"
@@ -150,15 +151,25 @@  static int __compare(const void *t1, const void *t2)
 		return 0;
 }
 
+static int __compare_core_cpu_list(const void *t1, const void *t2)
+{
+	struct cpuid_core_info *top1 = (struct cpuid_core_info *)t1;
+	struct cpuid_core_info *top2 = (struct cpuid_core_info *)t2;
+
+	return strcmp(top1->core_cpu_list, top2->core_cpu_list);
+}
+
 /*
  * Returns amount of cpus, negative on error, cpu_top must be
  * passed to cpu_topology_release to free resources
  *
- * Array is sorted after ->pkg, ->core, then ->cpu
+ * Array is sorted after ->cpu_smt_list ->pkg, ->core
  */
 int get_cpu_topology(struct cpupower_topology *cpu_top)
 {
 	int cpu, last_pkg, cpus = sysconf(_SC_NPROCESSORS_CONF);
+	char path[SYSFS_PATH_MAX];
+	char *last_cpu_list;
 
 	cpu_top->core_info = malloc(sizeof(struct cpuid_core_info) * cpus);
 	if (cpu_top->core_info == NULL)
@@ -183,6 +194,34 @@  int get_cpu_topology(struct cpupower_topology *cpu_top)
 			cpu_top->core_info[cpu].core = -1;
 			continue;
 		}
+		if (cpu_top->core_info[cpu].core == -1) {
+			strncpy(cpu_top->core_info[cpu].core_cpu_list, "-1", CPULIST_BUFFER);
+			continue;
+		}
+		snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/topology/%s",
+			 cpu, "core_cpus_list");
+		if (cpupower_read_sysfs(
+			path,
+			cpu_top->core_info[cpu].core_cpu_list,
+			CPULIST_BUFFER) < 1) {
+			printf("Warning CPU%u has a 0 size core_cpus_list string", cpu);
+		}
+	}
+
+	/* Count the number of distinct cpu lists to get the physical core
+	 * count.
+	 */
+	qsort(cpu_top->core_info, cpus, sizeof(struct cpuid_core_info),
+	      __compare_core_cpu_list);
+
+	last_cpu_list = cpu_top->core_info[0].core_cpu_list;
+	cpu_top->cores = 1;
+	for (cpu = 1; cpu < cpus; cpu++) {
+		if (strcmp(cpu_top->core_info[cpu].core_cpu_list, last_cpu_list) != 0 &&
+		    cpu_top->core_info[cpu].pkg != -1) {
+			last_cpu_list = cpu_top->core_info[cpu].core_cpu_list;
+			cpu_top->cores++;
+		}
 	}
 
 	qsort(cpu_top->core_info, cpus, sizeof(struct cpuid_core_info),
@@ -203,13 +242,6 @@  int get_cpu_topology(struct cpupower_topology *cpu_top)
 	if (!(cpu_top->core_info[0].pkg == -1))
 		cpu_top->pkgs++;
 
-	/* Intel's cores count is not consecutively numbered, there may
-	 * be a core_id of 3, but none of 2. Assume there always is 0
-	 * Get amount of cores by counting duplicates in a package
-	for (cpu = 0; cpu_top->core_info[cpu].pkg = 0 && cpu < cpus; cpu++) {
-		if (cpu_top->core_info[cpu].core == 0)
-	cpu_top->cores++;
-	*/
 	return cpus;
 }
 
diff --git a/tools/power/cpupower/lib/cpupower.h b/tools/power/cpupower/lib/cpupower.h
index e4e4292eacec..2e67a080f203 100644
--- a/tools/power/cpupower/lib/cpupower.h
+++ b/tools/power/cpupower/lib/cpupower.h
@@ -2,6 +2,8 @@ 
 #ifndef __CPUPOWER_CPUPOWER_H__
 #define __CPUPOWER_CPUPOWER_H__
 
+#define CPULIST_BUFFER 5
+
 struct cpupower_topology {
 	/* Amount of CPU cores, packages and threads per core in the system */
 	unsigned int cores;
@@ -16,6 +18,7 @@  struct cpuid_core_info {
 	int pkg;
 	int core;
 	int cpu;
+	char core_cpu_list[CPULIST_BUFFER];
 
 	/* flags */
 	unsigned int is_online:1;