[PATCH v4 10/11] perf/x86/rapl: Add per-core energy counter support for AMD CPUs

From: Dhananjay Ugwekar
Date: Thu Jul 11 2024 - 06:31:26 EST


Add a new "power_per_core" PMU and "energy-per-core" event for
monitoring energy consumption by each core. The existing energy-cores
event aggregates the energy consumption at the package level.
This new event aligns with the AMD's per_core energy counters.

Tested the package level and core level PMU counters with workloads
pinned to different CPUs.

Results with workload pinned to CPU 1 in core 1 on a AMD Zen4 Genoa
machine:

$ perf stat -a --per-core -e power_per_core/energy-per-core/ sleep 1

Performance counter stats for 'system wide':

S0-D0-C0 1 0.02 Joules power_per_core/energy-per-core/
S0-D0-C1 1 5.72 Joules power_per_core/energy-per-core/
S0-D0-C2 1 0.02 Joules power_per_core/energy-per-core/
S0-D0-C3 1 0.02 Joules power_per_core/energy-per-core/
S0-D0-C4 1 0.02 Joules power_per_core/energy-per-core/
S0-D0-C5 1 0.02 Joules power_per_core/energy-per-core/
S0-D0-C6 1 0.02 Joules power_per_core/energy-per-core/
S0-D0-C7 1 0.02 Joules power_per_core/energy-per-core/
S0-D0-C8 1 0.02 Joules power_per_core/energy-per-core/
S0-D0-C9 1 0.02 Joules power_per_core/energy-per-core/
S0-D0-C10 1 0.02 Joules power_per_core/energy-per-core/

Signed-off-by: Dhananjay Ugwekar <Dhananjay.Ugwekar@xxxxxxx>
---
Changes in v4:
* Reorder the cleanup order of per-core and per-pkg PMU (Rui)
* Add rapl_core_hw_unit variable to store the per-core PMU unit (Rui)
* Use the newly added hw_unit in rapl_scale() and rapl_advertise()
functions
* Add a new argument in rapl_scale() to differentiate between per-core
and per-pkg events.
---
arch/x86/events/rapl.c | 190 ++++++++++++++++++++++++++++++++++++-----
1 file changed, 171 insertions(+), 19 deletions(-)

diff --git a/arch/x86/events/rapl.c b/arch/x86/events/rapl.c
index 7691497042e9..91eb9850df9b 100644
--- a/arch/x86/events/rapl.c
+++ b/arch/x86/events/rapl.c
@@ -39,6 +39,10 @@
* event: rapl_energy_psys
* perf code: 0x5
*
+ * per_core counter: consumption of a single physical core
+ * event: rapl_energy_per_core (power_per_core PMU)
+ * perf code: 0x1
+ *
* We manage those counters as free running (read-only). They may be
* use simultaneously by other tools, such as turbostat.
*
@@ -81,6 +85,13 @@ enum perf_rapl_pkg_events {
NR_RAPL_PKG_DOMAINS = PERF_RAPL_PKG_EVENTS_MAX,
};

+enum perf_rapl_core_events {
+ PERF_RAPL_PER_CORE = 0, /* per-core */
+
+ PERF_RAPL_CORE_EVENTS_MAX,
+ NR_RAPL_CORE_DOMAINS = PERF_RAPL_CORE_EVENTS_MAX,
+};
+
static const char *const rapl_pkg_domain_names[NR_RAPL_PKG_DOMAINS] __initconst = {
"pp0-core",
"package",
@@ -89,6 +100,10 @@ static const char *const rapl_pkg_domain_names[NR_RAPL_PKG_DOMAINS] __initconst
"psys",
};

+static const char *const rapl_core_domain_names[NR_RAPL_CORE_DOMAINS] __initconst = {
+ "per-core",
+};
+
/*
* event code: LSB 8 bits, passed in attr->config
* any other bit is reserved
@@ -135,15 +150,20 @@ enum rapl_unit_quirk {

struct rapl_model {
struct perf_msr *rapl_pkg_msrs;
+ struct perf_msr *rapl_core_msrs;
unsigned long pkg_events;
+ unsigned long core_events;
unsigned int msr_power_unit;
enum rapl_unit_quirk unit_quirk;
};

/* 1/2^hw_unit Joule */
static int rapl_pkg_hw_unit[NR_RAPL_PKG_DOMAINS] __read_mostly;
+static int rapl_core_hw_unit __read_mostly;
static struct rapl_pmus *rapl_pmus_pkg;
+static struct rapl_pmus *rapl_pmus_core;
static unsigned int rapl_pkg_cntr_mask;
+static unsigned int rapl_core_cntr_mask;
static u64 rapl_timer_ms;
static struct rapl_model *rapl_model;

@@ -182,19 +202,25 @@ static inline u64 rapl_read_counter(struct perf_event *event)
return raw;
}

-static inline u64 rapl_scale(u64 v, int cfg)
+static inline u64 rapl_scale(u64 v, int cfg, bool per_core_event)
{
- if (cfg > NR_RAPL_PKG_DOMAINS) {
+ if ((!per_core_event && cfg > NR_RAPL_PKG_DOMAINS) ||
+ (per_core_event && cfg > NR_RAPL_CORE_DOMAINS)) {
pr_warn("Invalid domain %d, failed to scale data\n", cfg);
return v;
}
+ int hw_unit = rapl_pkg_hw_unit[cfg - 1];
+
+ if (per_core_event)
+ hw_unit = rapl_core_hw_unit;
+
/*
* scale delta to smallest unit (1/2^32)
* users must then scale back: count * 1/(1e9*2^32) to get Joules
* or use ldexp(count, -32).
* Watts = Joules/Time delta
*/
- return v << (32 - rapl_pkg_hw_unit[cfg - 1]);
+ return v << (32 - hw_unit);
}

static u64 rapl_event_update(struct perf_event *event)
@@ -203,6 +229,7 @@ static u64 rapl_event_update(struct perf_event *event)
u64 prev_raw_count, new_raw_count;
s64 delta, sdelta;
int shift = RAPL_CNTR_WIDTH;
+ struct rapl_pmus *curr_rapl_pmus;

prev_raw_count = local64_read(&hwc->prev_count);
do {
@@ -221,7 +248,12 @@ static u64 rapl_event_update(struct perf_event *event)
delta = (new_raw_count << shift) - (prev_raw_count << shift);
delta >>= shift;

- sdelta = rapl_scale(delta, event->hw.config);
+ curr_rapl_pmus = container_of(event->pmu, struct rapl_pmus, pmu);
+
+ if (curr_rapl_pmus == rapl_pmus_core)
+ sdelta = rapl_scale(delta, event->hw.config, true);
+ else
+ sdelta = rapl_scale(delta, event->hw.config, false);

local64_add(sdelta, &event->count);

@@ -352,9 +384,13 @@ static int rapl_pmu_event_init(struct perf_event *event)
u64 cfg = event->attr.config & RAPL_EVENT_MASK;
int bit, ret = 0;
struct rapl_pmu *rapl_pmu;
+ struct rapl_pmus *curr_rapl_pmus;

/* only look at RAPL events */
- if (event->attr.type != rapl_pmus_pkg->pmu.type)
+ if (event->attr.type == rapl_pmus_pkg->pmu.type ||
+ (rapl_pmus_core && event->attr.type == rapl_pmus_core->pmu.type))
+ curr_rapl_pmus = container_of(event->pmu, struct rapl_pmus, pmu);
+ else
return -ENOENT;

/* check only supported bits are set */
@@ -364,7 +400,8 @@ static int rapl_pmu_event_init(struct perf_event *event)
if (event->cpu < 0)
return -EINVAL;

- event->event_caps |= PERF_EV_CAP_READ_ACTIVE_PKG;
+ if (curr_rapl_pmus == rapl_pmus_pkg)
+ event->event_caps |= PERF_EV_CAP_READ_ACTIVE_PKG;

if (!cfg || cfg >= NR_RAPL_PKG_DOMAINS + 1)
return -EINVAL;
@@ -373,7 +410,8 @@ static int rapl_pmu_event_init(struct perf_event *event)
bit = cfg - 1;

/* check event supported */
- if (!(rapl_pkg_cntr_mask & (1 << bit)))
+ if (!(rapl_pkg_cntr_mask & (1 << bit)) &&
+ !(rapl_core_cntr_mask & (1 << bit)))
return -EINVAL;

/* unsupported modes and filters */
@@ -381,12 +419,18 @@ static int rapl_pmu_event_init(struct perf_event *event)
return -EINVAL;

/* must be done before validate_group */
- rapl_pmu = cpu_to_rapl_pmu(event->cpu);
+ if (curr_rapl_pmus == rapl_pmus_core) {
+ rapl_pmu = curr_rapl_pmus->rapl_pmu[topology_logical_core_id(event->cpu)];
+ event->hw.event_base = rapl_model->rapl_core_msrs[bit].msr;
+ } else {
+ rapl_pmu = curr_rapl_pmus->rapl_pmu[get_rapl_pmu_idx(event->cpu)];
+ event->hw.event_base = rapl_model->rapl_pkg_msrs[bit].msr;
+ }
+
if (!rapl_pmu)
return -EINVAL;
event->cpu = rapl_pmu->cpu;
event->pmu_private = rapl_pmu;
- event->hw.event_base = rapl_model->rapl_msrs[bit].msr;
event->hw.config = cfg;
event->hw.idx = bit;

@@ -415,17 +459,38 @@ static struct attribute_group rapl_pmu_attr_group = {
.attrs = rapl_pmu_attrs,
};

+static ssize_t rapl_get_attr_per_core_cpumask(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return cpumap_print_to_pagebuf(true, buf, rapl_pmus_core->cpumask);
+}
+
+static struct device_attribute dev_attr_per_core_cpumask = __ATTR(cpumask, 0444,
+ rapl_get_attr_per_core_cpumask,
+ NULL);
+
+static struct attribute *rapl_pmu_per_core_attrs[] = {
+ &dev_attr_per_core_cpumask.attr,
+ NULL,
+};
+
+static struct attribute_group rapl_pmu_per_core_attr_group = {
+ .attrs = rapl_pmu_per_core_attrs,
+};
+
RAPL_EVENT_ATTR_STR(energy-cores, rapl_cores, "event=0x01");
RAPL_EVENT_ATTR_STR(energy-pkg , rapl_pkg, "event=0x02");
RAPL_EVENT_ATTR_STR(energy-ram , rapl_ram, "event=0x03");
RAPL_EVENT_ATTR_STR(energy-gpu , rapl_gpu, "event=0x04");
RAPL_EVENT_ATTR_STR(energy-psys, rapl_psys, "event=0x05");
+RAPL_EVENT_ATTR_STR(energy-per-core, rapl_per_core, "event=0x01");

RAPL_EVENT_ATTR_STR(energy-cores.unit, rapl_cores_unit, "Joules");
RAPL_EVENT_ATTR_STR(energy-pkg.unit , rapl_pkg_unit, "Joules");
RAPL_EVENT_ATTR_STR(energy-ram.unit , rapl_ram_unit, "Joules");
RAPL_EVENT_ATTR_STR(energy-gpu.unit , rapl_gpu_unit, "Joules");
RAPL_EVENT_ATTR_STR(energy-psys.unit, rapl_psys_unit, "Joules");
+RAPL_EVENT_ATTR_STR(energy-per-core.unit, rapl_per_core_unit, "Joules");

/*
* we compute in 0.23 nJ increments regardless of MSR
@@ -435,6 +500,7 @@ RAPL_EVENT_ATTR_STR(energy-pkg.scale, rapl_pkg_scale, "2.3283064365386962890
RAPL_EVENT_ATTR_STR(energy-ram.scale, rapl_ram_scale, "2.3283064365386962890625e-10");
RAPL_EVENT_ATTR_STR(energy-gpu.scale, rapl_gpu_scale, "2.3283064365386962890625e-10");
RAPL_EVENT_ATTR_STR(energy-psys.scale, rapl_psys_scale, "2.3283064365386962890625e-10");
+RAPL_EVENT_ATTR_STR(energy-per-core.scale, rapl_per_core_scale, "2.3283064365386962890625e-10");

/*
* There are no default events, but we need to create
@@ -468,6 +534,13 @@ static const struct attribute_group *rapl_attr_groups[] = {
NULL,
};

+static const struct attribute_group *rapl_per_core_attr_groups[] = {
+ &rapl_pmu_per_core_attr_group,
+ &rapl_pmu_format_group,
+ &rapl_pmu_events_group,
+ NULL,
+};
+
static struct attribute *rapl_events_cores[] = {
EVENT_PTR(rapl_cores),
EVENT_PTR(rapl_cores_unit),
@@ -528,6 +601,18 @@ static struct attribute_group rapl_events_psys_group = {
.attrs = rapl_events_psys,
};

+static struct attribute *rapl_events_per_core[] = {
+ EVENT_PTR(rapl_per_core),
+ EVENT_PTR(rapl_per_core_unit),
+ EVENT_PTR(rapl_per_core_scale),
+ NULL,
+};
+
+static struct attribute_group rapl_events_per_core_group = {
+ .name = "events",
+ .attrs = rapl_events_per_core,
+};
+
static bool test_msr(int idx, void *data)
{
return test_bit(idx, (unsigned long *) data);
@@ -565,6 +650,11 @@ static struct perf_msr amd_rapl_pkg_msrs[] = {
[PERF_RAPL_PSYS] = { 0, &rapl_events_psys_group, NULL, false, 0 },
};

+static struct perf_msr amd_rapl_core_msrs[] = {
+ [PERF_RAPL_PER_CORE] = { MSR_AMD_CORE_ENERGY_STATUS, &rapl_events_per_core_group,
+ test_msr, false, RAPL_MSR_MASK },
+};
+
static int __rapl_cpu_offline(struct rapl_pmus *rapl_pmus, unsigned int rapl_pmu_idx,
const struct cpumask *event_cpumask, unsigned int cpu)
{
@@ -590,8 +680,14 @@ static int __rapl_cpu_offline(struct rapl_pmus *rapl_pmus, unsigned int rapl_pmu

static int rapl_cpu_offline(unsigned int cpu)
{
- return __rapl_cpu_offline(rapl_pmus_pkg, get_rapl_pmu_idx(cpu),
+ int ret = __rapl_cpu_offline(rapl_pmus_pkg, get_rapl_pmu_idx(cpu),
get_rapl_pmu_cpumask(cpu), cpu);
+
+ if (ret == 0 && rapl_model->core_events)
+ ret = __rapl_cpu_offline(rapl_pmus_core, topology_logical_core_id(cpu),
+ topology_sibling_cpumask(cpu), cpu);
+
+ return ret;
}

static int __rapl_cpu_online(struct rapl_pmus *rapl_pmus, unsigned int rapl_pmu_idx,
@@ -629,8 +725,14 @@ static int __rapl_cpu_online(struct rapl_pmus *rapl_pmus, unsigned int rapl_pmu_

static int rapl_cpu_online(unsigned int cpu)
{
- return __rapl_cpu_online(rapl_pmus_pkg, get_rapl_pmu_idx(cpu),
+ int ret = __rapl_cpu_online(rapl_pmus_pkg, get_rapl_pmu_idx(cpu),
get_rapl_pmu_cpumask(cpu), cpu);
+
+ if (ret == 0 && rapl_model->core_events)
+ ret = __rapl_cpu_online(rapl_pmus_core, topology_logical_core_id(cpu),
+ topology_sibling_cpumask(cpu), cpu);
+
+ return ret;
}


@@ -645,6 +747,8 @@ static int rapl_check_hw_unit(void)
for (i = 0; i < NR_RAPL_PKG_DOMAINS; i++)
rapl_pkg_hw_unit[i] = (msr_rapl_power_unit_bits >> 8) & 0x1FULL;

+ rapl_core_hw_unit = (msr_rapl_power_unit_bits >> 8) & 0x1FULL;
+
switch (rapl_model->unit_quirk) {
/*
* DRAM domain on HSW server and KNL has fixed energy unit which can be
@@ -684,7 +788,7 @@ static void __init rapl_advertise(void)
int i;

pr_info("API unit is 2^-32 Joules, %d fixed counters, %llu ms ovfl timer\n",
- hweight32(rapl_pkg_cntr_mask), rapl_timer_ms);
+ hweight32(rapl_pkg_cntr_mask) + hweight32(rapl_core_cntr_mask), rapl_timer_ms);

for (i = 0; i < NR_RAPL_PKG_DOMAINS; i++) {
if (rapl_pkg_cntr_mask & (1 << i)) {
@@ -692,6 +796,10 @@ static void __init rapl_advertise(void)
rapl_pkg_domain_names[i], rapl_pkg_hw_unit[i]);
}
}
+
+ if (rapl_core_cntr_mask & (1 << PERF_RAPL_PER_CORE))
+ pr_info("hw unit of domain %s 2^-%d Joules\n",
+ rapl_core_domain_names[PERF_RAPL_PER_CORE], rapl_core_hw_unit);
}

static void cleanup_rapl_pmus(struct rapl_pmus *rapl_pmus)
@@ -712,14 +820,16 @@ static const struct attribute_group *rapl_attr_update[] = {
NULL,
};

-static int __init init_rapl_pmus(struct rapl_pmus **rapl_pmus_ptr)
+static const struct attribute_group *rapl_per_core_attr_update[] = {
+ &rapl_events_per_core_group,
+};
+
+static int __init init_rapl_pmus(struct rapl_pmus **rapl_pmus_ptr, int nr_rapl_pmu,
+ const struct attribute_group **rapl_attr_groups,
+ const struct attribute_group **rapl_attr_update)
{
- int nr_rapl_pmu = topology_max_packages();
struct rapl_pmus *rapl_pmus;

- if (!rapl_pmu_is_pkg_scope())
- nr_rapl_pmu *= topology_max_dies_per_package();
-
rapl_pmus = kzalloc(struct_size(rapl_pmus, rapl_pmu, nr_rapl_pmu), GFP_KERNEL);
if (!rapl_pmus)
return -ENOMEM;
@@ -809,8 +919,10 @@ static struct rapl_model model_spr = {

static struct rapl_model model_amd_hygon = {
.pkg_events = BIT(PERF_RAPL_PKG),
+ .core_events = BIT(PERF_RAPL_PER_CORE),
.msr_power_unit = MSR_AMD_RAPL_POWER_UNIT,
.rapl_pkg_msrs = amd_rapl_pkg_msrs,
+ .rapl_core_msrs = amd_rapl_core_msrs,
};

static const struct x86_cpu_id rapl_model_match[] __initconst = {
@@ -867,6 +979,11 @@ static int __init rapl_pmu_init(void)
{
const struct x86_cpu_id *id;
int ret;
+ int nr_rapl_pmu = topology_max_packages() * topology_max_dies_per_package();
+ int nr_cores = topology_max_packages() * topology_num_cores_per_package();
+
+ if (rapl_pmu_is_pkg_scope())
+ nr_rapl_pmu = topology_max_packages();

id = x86_match_cpu(rapl_model_match);
if (!id)
@@ -874,17 +991,34 @@ static int __init rapl_pmu_init(void)

rapl_model = (struct rapl_model *) id->driver_data;

- rapl_pkg_cntr_mask = perf_msr_probe(rapl_model->rapl_msrs, PERF_RAPL_PKG_EVENTS_MAX,
+ rapl_pkg_cntr_mask = perf_msr_probe(rapl_model->rapl_pkg_msrs, PERF_RAPL_PKG_EVENTS_MAX,
false, (void *) &rapl_model->pkg_events);

ret = rapl_check_hw_unit();
if (ret)
return ret;

- ret = init_rapl_pmus(&rapl_pmus_pkg);
+ ret = init_rapl_pmus(&rapl_pmus_pkg, nr_rapl_pmu, rapl_attr_groups, rapl_attr_update);
if (ret)
return ret;

+ if (rapl_model->core_events) {
+ rapl_core_cntr_mask = perf_msr_probe(rapl_model->rapl_core_msrs,
+ PERF_RAPL_CORE_EVENTS_MAX, false,
+ (void *) &rapl_model->core_events);
+
+ ret = init_rapl_pmus(&rapl_pmus_core, nr_cores,
+ rapl_per_core_attr_groups, rapl_per_core_attr_update);
+ if (ret) {
+ /*
+ * If initialization of per_core PMU fails, reset per_core
+ * flag, and continue with power PMU initialization.
+ */
+ pr_warn("Per-core PMU initialization failed (%d)\n", ret);
+ rapl_model->core_events = 0UL;
+ }
+ }
+
/*
* Install callbacks. Core will call them for each online cpu.
*/
@@ -898,6 +1032,20 @@ static int __init rapl_pmu_init(void)
if (ret)
goto out1;

+ if (rapl_model->core_events) {
+ ret = perf_pmu_register(&rapl_pmus_core->pmu, "power_per_core", -1);
+ if (ret) {
+ /*
+ * If registration of per_core PMU fails, cleanup per_core PMU
+ * variables, reset the per_core flag and keep the
+ * power PMU untouched.
+ */
+ pr_warn("Per-core PMU registration failed (%d)\n", ret);
+ cleanup_rapl_pmus(rapl_pmus_core);
+ rapl_model->core_events = 0UL;
+ }
+ }
+
rapl_advertise();
return 0;

@@ -915,5 +1063,9 @@ static void __exit intel_rapl_exit(void)
cpuhp_remove_state_nocalls(CPUHP_AP_PERF_X86_RAPL_ONLINE);
+ if (rapl_model->core_events) {
+ perf_pmu_unregister(&rapl_pmus_core->pmu);
+ cleanup_rapl_pmus(rapl_pmus_core);
+ }
perf_pmu_unregister(&rapl_pmus_pkg->pmu);
cleanup_rapl_pmus(rapl_pmus_pkg);
}
module_exit(intel_rapl_exit);
--
2.34.1