perf evlist: Improve default event for s390

Frame pointer callchains are not supported on s390 and dwarf
callchains are only supported on software events.

Switch the default event from the hardware 'cycles' event to the
software 'cpu-clock' or 'task-clock' on s390 if callchains are
enabled. Move some of the target initialization earlier in builtin-top
and builtin-record, so it is ready for use by evlist__new_default.

If frame pointer callchains are requested on s390 show a
warning. Modify the '-g' option of `perf top` and `perf record` to
default to dwarf callchains on s390.

Signed-off-by: Ian Rogers <irogers@google.com>
Tested-by: Thomas Richter <tmricht@linux.ibm.com>
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
This commit is contained in:
Ian Rogers
2026-03-18 16:46:00 -07:00
committed by Namhyung Kim
parent c006753c3a
commit ca76fb67eb
9 changed files with 64 additions and 35 deletions

View File

@@ -55,6 +55,7 @@
#include "asm/bug.h"
#include "perf.h"
#include "cputopo.h"
#include "dwarf-regs.h"
#include <errno.h>
#include <inttypes.h>
@@ -2995,7 +2996,9 @@ static int record_callchain_opt(const struct option *opt,
return 0;
}
return record_opts__parse_callchain(opt->value, &callchain_param, "fp", unset);
return record_opts__parse_callchain(opt->value, &callchain_param,
EM_HOST != EM_S390 ? "fp" : "dwarf",
unset);
}
@@ -4095,8 +4098,11 @@ int cmd_record(int argc, const char **argv)
perf_debuginfod_setup(&record.debuginfod);
/* Make system wide (-a) the default target. */
if (!argc && target__none(&rec->opts.target))
/*
* Use system wide (-a) for the default target (ie. when no
* workload). User ID filtering also implies system-wide.
*/
if ((!argc && target__none(&rec->opts.target)) || rec->uid_str)
rec->opts.target.system_wide = true;
if (nr_cgroups && !rec->opts.target.system_wide) {
@@ -4274,7 +4280,8 @@ int cmd_record(int argc, const char **argv)
record.opts.tail_synthesize = true;
if (rec->evlist->core.nr_entries == 0) {
struct evlist *def_evlist = evlist__new_default();
struct evlist *def_evlist = evlist__new_default(&rec->opts.target,
callchain_param.enabled);
if (!def_evlist)
goto out;
@@ -4303,9 +4310,6 @@ int cmd_record(int argc, const char **argv)
err = parse_uid_filter(rec->evlist, uid);
if (err)
goto out;
/* User ID filtering implies system wide. */
rec->opts.target.system_wide = true;
}
/* Enable ignoring missing threads when -p option is defined. */

View File

@@ -56,6 +56,7 @@
#include "util/debug.h"
#include "util/ordered-events.h"
#include "util/pfm.h"
#include "dwarf-regs.h"
#include <assert.h>
#include <elf.h>
@@ -1420,7 +1421,7 @@ callchain_opt(const struct option *opt, const char *arg __maybe_unused, int unse
return 0;
}
return parse_callchain_opt(opt, "fp", unset);
return parse_callchain_opt(opt, EM_HOST != EM_S390 ? "fp" : "dwarf", unset);
}
@@ -1705,8 +1706,17 @@ int cmd_top(int argc, const char **argv)
if (annotate_check_args() < 0)
goto out_delete_evlist;
status = target__validate(target);
if (status) {
target__strerror(target, status, errbuf, BUFSIZ);
ui__warning("%s\n", errbuf);
}
if (target__none(target))
target->system_wide = true;
if (!top.evlist->core.nr_entries) {
struct evlist *def_evlist = evlist__new_default();
struct evlist *def_evlist = evlist__new_default(target, callchain_param.enabled);
if (!def_evlist)
goto out_delete_evlist;
@@ -1799,12 +1809,6 @@ int cmd_top(int argc, const char **argv)
goto out_delete_evlist;
}
status = target__validate(target);
if (status) {
target__strerror(target, status, errbuf, BUFSIZ);
ui__warning("%s\n", errbuf);
}
if (top.uid_str) {
uid_t uid = parse_uid(top.uid_str);
@@ -1818,9 +1822,6 @@ int cmd_top(int argc, const char **argv)
goto out_delete_evlist;
}
if (target__none(target))
target->system_wide = true;
if (evlist__create_maps(top.evlist, target) < 0) {
ui__error("Couldn't create thread/CPU maps: %s\n",
errno == ENOENT ? "No such process" : str_error_r(errno, errbuf, sizeof(errbuf)));

View File

@@ -8,6 +8,7 @@
#include "header.h"
#include "machine.h"
#include "util/synthetic-events.h"
#include "target.h"
#include "tool.h"
#include "tests.h"
#include "debug.h"
@@ -81,7 +82,8 @@ static int test__event_update(struct test_suite *test __maybe_unused, int subtes
{
struct evsel *evsel;
struct event_name tmp;
struct evlist *evlist = evlist__new_default();
struct target target = {};
struct evlist *evlist = evlist__new_default(&target, /*sample_callchains=*/false);
TEST_ASSERT_VAL("failed to get evlist", evlist);

View File

@@ -8,6 +8,7 @@
#include "parse-events.h"
#include "pmu-events/pmu-events.h"
#include "pfm.h"
#include "target.h"
#include <subcmd/parse-options.h>
#include <stdio.h>
#include <stdlib.h>
@@ -99,7 +100,8 @@ out: for (i = 0; i < nr_events; i++)
static int expand_default_events(void)
{
int ret;
struct evlist *evlist = evlist__new_default();
struct target target = {};
struct evlist *evlist = evlist__new_default(&target, /*sample_callchains=*/false);
TEST_ASSERT_VAL("failed to get evlist", evlist);

View File

@@ -84,8 +84,11 @@ static int test__PERF_RECORD(struct test_suite *test __maybe_unused, int subtest
CPU_ZERO_S(cpu_mask_size, cpu_mask);
perf_sample__init(&sample, /*all=*/false);
if (evlist == NULL) /* Fallback for kernels lacking PERF_COUNT_SW_DUMMY */
evlist = evlist__new_default();
if (evlist == NULL) { /* Fallback for kernels lacking PERF_COUNT_SW_DUMMY */
struct target target = {};
evlist = evlist__new_default(&target, /*sample_callchains=*/false);
}
if (evlist == NULL) {
pr_debug("Not enough memory to create evlist\n");

View File

@@ -9,6 +9,7 @@
#include "evlist.h"
#include "debug.h"
#include "pmus.h"
#include "target.h"
#include <linux/err.h>
#define TEMPL "/tmp/perf-test-XXXXXX"
@@ -37,11 +38,12 @@ static int session_write_header(char *path)
.path = path,
.mode = PERF_DATA_MODE_WRITE,
};
struct target target = {};
session = perf_session__new(&data, NULL);
TEST_ASSERT_VAL("can't get session", !IS_ERR(session));
session->evlist = evlist__new_default();
session->evlist = evlist__new_default(&target, /*sample_callchains=*/false);
TEST_ASSERT_VAL("can't get evlist", session->evlist);
session->evlist->session = session;

View File

@@ -13,6 +13,7 @@
#include "util/mmap.h"
#include "thread_map.h"
#include "target.h"
#include "dwarf-regs.h"
#include "evlist.h"
#include "evsel.h"
#include "record.h"
@@ -98,38 +99,47 @@ struct evlist *evlist__new(void)
return evlist;
}
struct evlist *evlist__new_default(void)
struct evlist *evlist__new_default(const struct target *target, bool sample_callchains)
{
struct evlist *evlist = evlist__new();
bool can_profile_kernel;
struct perf_pmu *pmu = NULL;
struct evsel *evsel;
char buf[256];
int err;
if (!evlist)
return NULL;
can_profile_kernel = perf_event_paranoid_check(1);
while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
char buf[256];
int err;
snprintf(buf, sizeof(buf), "%s/cycles/%s", pmu->name,
if (EM_HOST == EM_S390 && sample_callchains) {
snprintf(buf, sizeof(buf), "software/%s/%s",
target__has_cpu(target) ? "cpu-clock" : "task-clock",
can_profile_kernel ? "P" : "Pu");
err = parse_event(evlist, buf);
if (err) {
evlist__delete(evlist);
return NULL;
if (err)
goto out_err;
} else {
while ((pmu = perf_pmus__scan_core(pmu)) != NULL) {
snprintf(buf, sizeof(buf), "%s/cycles/%s", pmu->name,
can_profile_kernel ? "P" : "Pu");
err = parse_event(evlist, buf);
if (err)
goto out_err;
}
}
/* If there is only 1 event a sample identifier isn't necessary. */
if (evlist->core.nr_entries > 1) {
struct evsel *evsel;
evlist__for_each_entry(evlist, evsel)
evsel__set_sample_id(evsel, /*can_sample_identifier=*/false);
}
return evlist;
out_err:
evlist__delete(evlist);
return NULL;
}
struct evlist *evlist__new_dummy(void)

View File

@@ -104,7 +104,7 @@ struct evsel_str_handler {
};
struct evlist *evlist__new(void);
struct evlist *evlist__new_default(void);
struct evlist *evlist__new_default(const struct target *target, bool sample_callchains);
struct evlist *evlist__new_dummy(void);
void evlist__init(struct evlist *evlist, struct perf_cpu_map *cpus,
struct perf_thread_map *threads);

View File

@@ -1021,6 +1021,11 @@ static void __evsel__config_callchain(struct evsel *evsel, const struct record_o
bool function = evsel__is_function_event(evsel);
struct perf_event_attr *attr = &evsel->core.attr;
if (EM_HOST == EM_S390 && param->record_mode == CALLCHAIN_FP) {
pr_warning_once(
"Framepointer unwinding lacks kernel support. Use '--call-graph dwarf'\n");
}
evsel__set_sample_bit(evsel, CALLCHAIN);
attr->sample_max_stack = param->max_stack;