Re: [PATCH v4 3/6] perf header: Support memory ranges
"Falcon, Thomas" <[email protected]>
| Newsgroups | org.kernel.vger.linux-perf-users,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
On Mon, 2026-08-17 at 14:34 -0700, Ian Rogers wrote: > On Tue, Aug 11, 2026 at 10:33 AM Thomas Falcon > <[email protected]> wrote: > > > > 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/[email protected]/ > > > > Assisted-by: Sashiko:gemini-3.1-pro-preview > > Assisted-by: GitHub-Copilot:claude-opus-4-8 > > Signed-off-by: Thomas Falcon <[email protected]> > > Looks good to me. Did you do pipe mode testing? Is there any > additional testing coverage we can add? > > Thanks, > Ian > Hi Ian, thanks for reviewing, Yes, here is some example output in pipe mode: ./perf mem record -o - -a sleep 5 | ./perf script -i - --header ... # 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 perf 12280 [007] 82283.852958: 63 cpu/mem-loads,ldlat=30/P: ff40225e7b1acfb8 10e6a100042 |OP LOAD|LVL L0 hit|SNP None|TLB L1 or L2 hit|LCK Yes|BLK N/A|Region N/A 4 302 17 ffffffff9eafe901 perf_event_ctx_lock_nested.isra.0+0x31> swapper 0 [000] 82283.858694: 63 cpu/mem-loads,ldlat=30/P: ff40225e7d0b31c0 810668100842 |OP LOAD|LVL L3 hit|SNP None|TLB L1 or L2 hit|LCK No|BLK N/A|Region Local-shared-cache 60 62 0 ffffffff9e858b3f update_sg_lb_stats+0x13f> swapper 0 [000] 82283.878498: 63 cpu/mem-loads,ldlat=30/P: ff40225e7d9b3f98 1810668800842 |OP LOAD|LVL L3 hit|SNP HitM|TLB L1 or L2 hit|LCK No|BLK N/A|Region Other-non-shared-cache 434 435 0 ffffffff9e863e80 _nohz_idle_balance.i> perf-exec 12283 [023] 82283.882372: 63 cpu/mem-loads,ldlat=30/P: ffe698a847e86408 2011a68101042 |OP LOAD|LVL RAM hit|SNP None|TLB L1 or L2 hit|LCK No|BLK N/A|Region Mem-0 164 165 0 ffffffff9eb97335 zap_present_ptes.con> I think we could test that perf c2c/script show the "Region..." field and perf c2c report --stdio and perf report/script --header print the memory range table, if supported. Thanks, Tom > > --- > > v4: > > -- Fixed bounds checking for local and remote region IDs in > > memory_range__read(), which made region "255" invalid > > > > v3: > > -- Added missing ff->size check in process_memory_ranges() > > -- used open/openat to read memory range sysfs files in > > memory_range__read() > > -- Removed path name and file name buffers in memory_range__read(), > > instead pass path name from as a parameter from > > memory_range__parse() > > -- use calloc instead of zalloc in memory_range__parse() and > > process_memory_ranges() > > -- Removed redundant check for existance of memory range directory > > in > > memory_range__read() > > -- Included examples of memory range output in commit message > > > > v2: > > -- Added check for NULL return of sysfs__mountpoint() when parsing > > memory ranges in sysfs > > -- increased MAX_MEMORY_RANGES sanity check from 64 to 256 based on > > ACPI MRRM table implementation in Linux kernel > > -- added comment for MAX_MEMORY_RANGES to clarify that it is a > > sanity check > > for malformed perf.data files > > -- removed a line of code was removed from util/env.h but was added > > back in > > v1 due to bad rebase > > --- > > > > .../Documentation/perf.data-file-format.txt | 13 ++ > > tools/perf/builtin-inject.c | 1 + > > tools/perf/util/env.c | 1 + > > tools/perf/util/env.h | 10 + > > tools/perf/util/header.c | 203 > > ++++++++++++++++++ > > tools/perf/util/header.h | 1 + > > 6 files changed, 229 insertions(+) > > > > diff --git a/tools/perf/Documentation/perf.data-file-format.txt > > b/tools/perf/Documentation/perf.data-file-format.txt > > index b90cba9168f8..1f8e7d8e9ae2 100644 > > --- a/tools/perf/Documentation/perf.data-file-format.txt > > +++ b/tools/perf/Documentation/perf.data-file-format.txt > > @@ -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, > > > > diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin- > > inject.c > > index 70bbfad5653e..169617a884d3 100644 > > --- a/tools/perf/builtin-inject.c > > +++ b/tools/perf/builtin-inject.c > > @@ -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: > > diff --git a/tools/perf/util/env.c b/tools/perf/util/env.c > > index c0e2b9d5f0b2..50993b0dedc5 100644 > > --- a/tools/perf/util/env.c > > +++ b/tools/perf/util/env.c > > @@ -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); > > } > > > > diff --git a/tools/perf/util/env.h b/tools/perf/util/env.h > > index 7acca39b42ff..23d35425efe3 100644 > > --- a/tools/perf/util/env.h > > +++ b/tools/perf/util/env.h > > @@ -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 > > diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c > > index e90e541f546b..1dbc83a2d798 100644 > > --- a/tools/perf/util/header.c > > +++ b/tools/perf/util/header.c > > @@ -92,6 +92,7 @@ > > #define MAX_PMU_CAPS 512 > > #define MAX_PMU_MAPPINGS 4096 > > #define MAX_SCHED_DOMAINS 64 > > +#define MAX_MEMORY_RANGES 256 > > > > /* > > * magic2 = "PERFILE2" > > @@ -1891,6 +1892,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); > > @@ -2628,6 +2755,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) > > @@ -4201,6 +4345,64 @@ 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), \ > > @@ -4265,6 +4467,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 { > > diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h > > index 5e03f884b7cc..765037762758 100644 > > --- a/tools/perf/util/header.h > > +++ b/tools/perf/util/header.h > > @@ -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, > > }; > > -- > > 2.55.0 > >