mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
perf header: Support memory ranges
Memory ranges were created to track different types of memory, such as persistent, high bandwidth, or CXL-attached, that may be present on a system for performance monitoring and resource control purposes. Memory range data are parsed from the ACPI MRRM table and exposed to userspace tools via sysfs [1]. Memory range data is read from: /sys/firmware/acpi/memory_ranges/rangeX With the following attributes: u64 base; u64 length; int node; u8 local_region_id; u8 remote_region_id; Read memory range data from sysfs if present and save it in the header of the perf data file under a new feature bit, HEADER_MEMORY_RANGES (35). Memory range data can be viewed with the --header or --header-only options of perf-report and perf-script. Example output: # memory ranges (nr 5): # range0: [0x0000000000000000-0x00000000bfffffff], node = 0, local_region_id = 0, remote_region_id = 255 # range1: [0x0000000100000000-0x000000203fffffff], node = 0, local_region_id = 0, remote_region_id = 255 # range2: [0x0000008000000000-0x0000027fffffffff], node = -2, local_region_id = 1, remote_region_id = 255 # range3: [0x0000028000000000-0x0000047fffffffff], node = -2, local_region_id = 1, remote_region_id = 255 # range4: [0x0000048000000000-0x000004ffffffffff], node = -2, local_region_id = 1, remote_region_id = 255 [1]: https://lore.kernel.org/lkml/20250505173819.419271-1-tony.luck@intel.com/ Reviewed-by: Ian Rogers <irogers@google.com> Reviewed-by: Namhyung Kim <namhyung@kernel.org> Assisted-by: GitHub-Copilot:claude-opus-4-8 Assisted-by: Sashiko:gemini-3.1-pro-preview Signed-off-by: Thomas Falcon <thomas.falcon@intel.com> Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
parent
f6659329a4
commit
6cdecad561
|
|
@ -478,6 +478,19 @@ The size of the cacheline in bytes. Format:
|
|||
|
||||
unsigned int cln_size;
|
||||
|
||||
HEADER_MEMORY_RANGES = 35,
|
||||
|
||||
List of memory ranges. The format of the data is as below.
|
||||
|
||||
u32 nr_memory_ranges;
|
||||
struct memory_range {
|
||||
u64 base;
|
||||
u64 length;
|
||||
int node;
|
||||
u8 local_region_id;
|
||||
u8 remote_region_id;
|
||||
}; [nr_memory_ranges]
|
||||
|
||||
other bits are reserved and should be ignored for now
|
||||
HEADER_FEAT_BITS = 256,
|
||||
|
||||
|
|
|
|||
|
|
@ -2306,6 +2306,7 @@ static bool keep_feat(struct perf_inject *inject, int feat)
|
|||
case HEADER_PMU_CAPS:
|
||||
case HEADER_CPU_DOMAIN_INFO:
|
||||
case HEADER_CLN_SIZE:
|
||||
case HEADER_MEMORY_RANGES:
|
||||
return true;
|
||||
/* Information that can be updated */
|
||||
case HEADER_BUILD_ID:
|
||||
|
|
|
|||
|
|
@ -297,6 +297,7 @@ void perf_env__exit(struct perf_env *env)
|
|||
zfree(&env->pmu_caps[i].pmu_name);
|
||||
}
|
||||
zfree(&env->pmu_caps);
|
||||
zfree(&env->memory_ranges);
|
||||
free_cpu_domain_info(env->cpu_domain, env->schedstat_version, env->nr_cpus_avail);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,14 @@ struct cpu_domain_map {
|
|||
struct domain_info **domains;
|
||||
};
|
||||
|
||||
struct memory_range {
|
||||
u64 base;
|
||||
u64 length;
|
||||
int node;
|
||||
u8 local_region_id;
|
||||
u8 remote_region_id;
|
||||
};
|
||||
|
||||
struct perf_env {
|
||||
char *hostname;
|
||||
char *os_release;
|
||||
|
|
@ -100,6 +108,7 @@ struct perf_env {
|
|||
int nr_cpu_pmu_caps;
|
||||
int nr_hybrid_nodes;
|
||||
int nr_pmus_with_caps;
|
||||
int nr_memory_ranges;
|
||||
char *cmdline;
|
||||
const char **cmdline_argv;
|
||||
char *sibling_cores;
|
||||
|
|
@ -122,6 +131,7 @@ struct perf_env {
|
|||
unsigned long long memory_bsize;
|
||||
struct hybrid_node *hybrid_nodes;
|
||||
struct pmu_caps *pmu_caps;
|
||||
struct memory_range *memory_ranges;
|
||||
#ifdef HAVE_LIBBPF_SUPPORT
|
||||
/*
|
||||
* bpf_info_lock protects bpf rbtrees. This is needed because the
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <byteswap.h>
|
||||
#include <dirent.h>
|
||||
|
|
@ -95,6 +96,7 @@
|
|||
#define MAX_PMU_CAPS 512
|
||||
#define MAX_PMU_MAPPINGS 4096
|
||||
#define MAX_SCHED_DOMAINS 64
|
||||
#define MAX_MEMORY_RANGES 256
|
||||
|
||||
/*
|
||||
* magic2 = "PERFILE2"
|
||||
|
|
@ -1914,6 +1916,132 @@ static int write_cpu_domain_info(struct feat_fd *ff,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int memory_range__read(struct memory_range *range, const char *path)
|
||||
{
|
||||
char buf[32];
|
||||
ssize_t n;
|
||||
int dfd, fd, tmp, ret = -1;
|
||||
|
||||
dfd = open(path, O_RDONLY | O_DIRECTORY);
|
||||
if (dfd < 0)
|
||||
return -1;
|
||||
|
||||
#define _R(name, dst, conv) \
|
||||
fd = openat(dfd, name, O_RDONLY); \
|
||||
if (fd < 0) \
|
||||
goto out; \
|
||||
n = read(fd, buf, sizeof(buf) - 1); \
|
||||
close(fd); \
|
||||
if (n <= 0) \
|
||||
goto out; \
|
||||
buf[n] = '\0'; \
|
||||
dst = conv(buf, NULL, 0);
|
||||
|
||||
_R("base", range->base, strtoull);
|
||||
_R("length", range->length, strtoull);
|
||||
_R("node", range->node, strtol);
|
||||
_R("local_region_id", tmp, strtol);
|
||||
if (tmp < 0 || tmp > UINT8_MAX)
|
||||
goto out;
|
||||
range->local_region_id = tmp;
|
||||
_R("remote_region_id", tmp, strtol);
|
||||
if (tmp < 0 || tmp > UINT8_MAX)
|
||||
goto out;
|
||||
range->remote_region_id = tmp;
|
||||
#undef _R
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
close(dfd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int memory_range__parse(struct memory_range **ranges)
|
||||
{
|
||||
const char *sysfs = sysfs__mountpoint();
|
||||
int i, err, nr_memory_ranges = 0;
|
||||
char path[PATH_MAX];
|
||||
struct stat st;
|
||||
|
||||
if (!sysfs)
|
||||
return 0;
|
||||
|
||||
scnprintf(path, PATH_MAX, "%s/firmware/acpi/memory_ranges", sysfs);
|
||||
if (stat(path, &st))
|
||||
return 0;
|
||||
|
||||
while (1) {
|
||||
scnprintf(path, PATH_MAX,
|
||||
"%s/firmware/acpi/memory_ranges/range%d",
|
||||
sysfs, nr_memory_ranges);
|
||||
if (stat(path, &st))
|
||||
break;
|
||||
|
||||
nr_memory_ranges++;
|
||||
}
|
||||
|
||||
if (nr_memory_ranges == 0)
|
||||
return 0;
|
||||
|
||||
*ranges = calloc(nr_memory_ranges, sizeof(struct memory_range));
|
||||
if (!(*ranges))
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < nr_memory_ranges; i++) {
|
||||
struct memory_range range;
|
||||
|
||||
scnprintf(path, PATH_MAX,
|
||||
"%s/firmware/acpi/memory_ranges/range%d", sysfs, i);
|
||||
err = memory_range__read(&range, path);
|
||||
if (err < 0)
|
||||
goto out_error;
|
||||
|
||||
(*ranges)[i] = range;
|
||||
}
|
||||
|
||||
return nr_memory_ranges;
|
||||
|
||||
out_error:
|
||||
zfree(ranges);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int write_memory_ranges(struct feat_fd *ff,
|
||||
struct evlist *evlist __maybe_unused)
|
||||
{
|
||||
struct memory_range *ranges = NULL;
|
||||
int nr_memory_ranges = 0, ret;
|
||||
|
||||
nr_memory_ranges = memory_range__parse(&ranges);
|
||||
if (nr_memory_ranges < 0)
|
||||
return nr_memory_ranges;
|
||||
|
||||
ret = do_write(ff, &nr_memory_ranges, sizeof(nr_memory_ranges));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
for (int i = 0; i < nr_memory_ranges; i++) {
|
||||
ret = do_write(ff, &ranges[i].base, sizeof(u64));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
ret = do_write(ff, &ranges[i].length, sizeof(u64));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
ret = do_write(ff, &ranges[i].node, sizeof(u32));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
ret = do_write(ff, &ranges[i].local_region_id, sizeof(u8));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
ret = do_write(ff, &ranges[i].remote_region_id, sizeof(u8));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
out:
|
||||
zfree(&ranges);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void print_hostname(struct feat_fd *ff, FILE *fp)
|
||||
{
|
||||
fprintf(fp, "# hostname : %s\n", ff->ph->env.hostname);
|
||||
|
|
@ -2652,6 +2780,23 @@ static void print_cpu_domain_info(struct feat_fd *ff, FILE *fp)
|
|||
}
|
||||
}
|
||||
|
||||
static void print_memory_ranges(struct feat_fd *ff, FILE *fp)
|
||||
{
|
||||
struct memory_range *ranges = ff->ph->env.memory_ranges;
|
||||
int nr_memory_ranges = ff->ph->env.nr_memory_ranges;
|
||||
int i;
|
||||
|
||||
fprintf(fp, "# memory ranges (nr %d):\n", nr_memory_ranges);
|
||||
|
||||
for (i = 0; i < nr_memory_ranges; i++) {
|
||||
fprintf(fp, "# range%u: [0x%016" PRIx64 "-0x%016" PRIx64,
|
||||
i, ranges[i].base, ranges[i].base + ranges[i].length - 1);
|
||||
fprintf(fp, "], node = %d, local_region_id = %d, remote_region_id = %d\n",
|
||||
ranges[i].node, ranges[i].local_region_id,
|
||||
ranges[i].remote_region_id);
|
||||
}
|
||||
}
|
||||
|
||||
static int __event_process_build_id(struct perf_record_header_build_id *bev,
|
||||
char *filename,
|
||||
struct perf_session *session)
|
||||
|
|
@ -4227,6 +4372,65 @@ static int process_cpu_domain_info(struct feat_fd *ff, void *data __maybe_unused
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int process_memory_ranges(struct feat_fd *ff, void *data __maybe_unused)
|
||||
{
|
||||
struct perf_env *env = &ff->ph->env;
|
||||
struct memory_range *ranges, *r;
|
||||
u32 nr_memory_ranges, i;
|
||||
|
||||
if (do_read_u32(ff, &nr_memory_ranges))
|
||||
return -1;
|
||||
|
||||
if (!nr_memory_ranges) {
|
||||
pr_debug("memory ranges not available\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* According to version 1.1 of the ACPI MRRM table, the maximum
|
||||
* number of memory regions can be at most 255. Do a sanity check
|
||||
* here to guard against a malformed perf.data file.
|
||||
*/
|
||||
if (nr_memory_ranges > MAX_MEMORY_RANGES) {
|
||||
pr_err("Invalid memory_ranges: nr_memory_ranges (%u) > %u\n",
|
||||
nr_memory_ranges, MAX_MEMORY_RANGES);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ff->size < sizeof(u32) + nr_memory_ranges * (2 * sizeof(u64) + sizeof(u32) + 2 * sizeof(u8))) {
|
||||
pr_err("Invalid HEADER_MEMORY_RANGES: section too small (%zu) for %u range entries\n",
|
||||
ff->size, nr_memory_ranges);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ranges = calloc(nr_memory_ranges, sizeof(*ranges));
|
||||
if (!ranges)
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < nr_memory_ranges; i++) {
|
||||
r = &ranges[i];
|
||||
|
||||
if (do_read_u64(ff, &r->base))
|
||||
goto error;
|
||||
if (do_read_u64(ff, &r->length))
|
||||
goto error;
|
||||
if (do_read_u32(ff, (u32 *) &r->node))
|
||||
goto error;
|
||||
if (__do_read(ff, &r->local_region_id, sizeof(u8)))
|
||||
goto error;
|
||||
if (__do_read(ff, &r->remote_region_id, sizeof(u8)))
|
||||
goto error;
|
||||
}
|
||||
|
||||
env->memory_ranges = ranges;
|
||||
env->nr_memory_ranges = nr_memory_ranges;
|
||||
|
||||
return 0;
|
||||
error:
|
||||
zfree(&ranges);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define FEAT_OPR(n, func, __full_only) \
|
||||
[HEADER_##n] = { \
|
||||
.name = __stringify(n), \
|
||||
|
|
@ -4291,6 +4495,7 @@ const struct perf_header_feature_ops feat_ops[HEADER_LAST_FEATURE] = {
|
|||
FEAT_OPR(CPU_DOMAIN_INFO, cpu_domain_info, true),
|
||||
FEAT_OPR(E_MACHINE, e_machine, false),
|
||||
FEAT_OPR(CLN_SIZE, cln_size, false),
|
||||
FEAT_OPR(MEMORY_RANGES, memory_ranges, false),
|
||||
};
|
||||
|
||||
struct header_print_data {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ enum {
|
|||
HEADER_CPU_DOMAIN_INFO,
|
||||
HEADER_E_MACHINE,
|
||||
HEADER_CLN_SIZE,
|
||||
HEADER_MEMORY_RANGES,
|
||||
HEADER_LAST_FEATURE,
|
||||
HEADER_FEAT_BITS = 256,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user