2022-08-30 16:50:33

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v1 0/8] Add core wide metric literal

It is possible to optimize metrics when all SMT threads (CPUs) on a
core are measuring events in system wide mode. For example, TMA
metrics [1] defines CORE_CLKS for Sandybrdige as:

if SMT is disabled:
CPU_CLK_UNHALTED.THREAD
if SMT is enabled and recording on all SMT threads (for all processes):
CPU_CLK_UNHALTED.THREAD_ANY / 2
if SMT is enabled and not recording on all SMT threads:
(CPU_CLK_UNHALTED.THREAD/2)*
(1+CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE/CPU_CLK_UNHALTED.REF_XCLK )

That is two more events are necessary when not gathering counts on all
SMT threads. To distinguish all SMT threads on a core vs system wide
(all CPUs) call the new property core wide.

As this literal requires the user requested CPUs and system wide to be
present, the parsing of metrics is delayed until after command line
option processing. As events are used to compute the evlist maps, and
metrics create events, the data for core wide must come from the target.

This patch series doesn't correct the Intel metrics to use #core_wide,
which will be done in follow up work. To see the two behaviors
currently you need an Intel CPU between Sandybridge and before
Icelake, then compare the events for tma_backend_bound_percent and
Backend_Bound_SMT where the former assumes recording on all SMT
threads and the latter assumes not recording on all SMT threads. The
future work will just have a single backend bound metric for both
cases determined using #core_wide.

[1] https://download.01.org/perfmon/TMA_Metrics.xlsx Note, #EBS_Mode
is false when recording on all SMT threads and all processes which is
#core_wide true in this change.

Ian Rogers (8):
perf smt: Tidy header guard add SPDX
perf metric: Return early if no CPU PMU table exists
perf expr: Move the scanner_ctx into the parse_ctx
perf smt: Compute SMT from topology
perf topology: Add core_wide
perf stat: Delay metric parsing
perf metrics: Wire up core_wide
perf test: Add basic core_wide expression test

tools/perf/builtin-stat.c | 57 +++++++++++++-----
tools/perf/tests/expr.c | 37 +++++++++---
tools/perf/util/cputopo.c | 61 +++++++++++++++++++
tools/perf/util/cputopo.h | 5 ++
tools/perf/util/expr.c | 26 ++++----
tools/perf/util/expr.h | 14 +++--
tools/perf/util/expr.l | 6 +-
tools/perf/util/metricgroup.c | 92 +++++++++++++++++++++-------
tools/perf/util/metricgroup.h | 4 +-
tools/perf/util/smt.c | 110 ++++++++--------------------------
tools/perf/util/smt.h | 19 ++++--
tools/perf/util/stat-shadow.c | 13 ++--
tools/perf/util/stat.h | 2 +
13 files changed, 286 insertions(+), 160 deletions(-)

--
2.37.2.672.g94769d06f0-goog


2022-08-30 16:51:56

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v1 4/8] perf smt: Compute SMT from topology

The topology records sibling threads. Rather than computing SMT using
siblings in sysfs, reuse the values in topology. This only applies
when the file smt/active isn't available.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/tests/expr.c | 24 ++++++----
tools/perf/util/cputopo.c | 15 +++++++
tools/perf/util/cputopo.h | 2 +
tools/perf/util/expr.c | 9 ++--
tools/perf/util/smt.c | 95 ++++-----------------------------------
tools/perf/util/smt.h | 5 ++-
6 files changed, 49 insertions(+), 101 deletions(-)

diff --git a/tools/perf/tests/expr.c b/tools/perf/tests/expr.c
index 7ca5e37de560..db736ed49556 100644
--- a/tools/perf/tests/expr.c
+++ b/tools/perf/tests/expr.c
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-2.0
+#include "util/cputopo.h"
#include "util/debug.h"
#include "util/expr.h"
#include "util/header.h"
@@ -154,15 +155,20 @@ static int test__expr(struct test_suite *t __maybe_unused, int subtest __maybe_u
(void **)&val_ptr));

/* Only EVENT1 or EVENT2 need be measured depending on the value of smt_on. */
- expr__ctx_clear(ctx);
- TEST_ASSERT_VAL("find ids",
- expr__find_ids("EVENT1 if #smt_on else EVENT2",
- NULL, ctx) == 0);
- TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1);
- TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids,
- smt_on() ? "EVENT1" : "EVENT2",
- (void **)&val_ptr));
-
+ {
+ struct cpu_topology *topology = cpu_topology__new();
+ bool smton = smt_on(topology);
+
+ cpu_topology__delete(topology);
+ expr__ctx_clear(ctx);
+ TEST_ASSERT_VAL("find ids",
+ expr__find_ids("EVENT1 if #smt_on else EVENT2",
+ NULL, ctx) == 0);
+ TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1);
+ TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids,
+ smton ? "EVENT1" : "EVENT2",
+ (void **)&val_ptr));
+ }
/* The expression is a constant 1.0 without needing to evaluate EVENT1. */
expr__ctx_clear(ctx);
TEST_ASSERT_VAL("find ids",
diff --git a/tools/perf/util/cputopo.c b/tools/perf/util/cputopo.c
index d275d843c155..511002e52714 100644
--- a/tools/perf/util/cputopo.c
+++ b/tools/perf/util/cputopo.c
@@ -157,6 +157,21 @@ void cpu_topology__delete(struct cpu_topology *tp)
free(tp);
}

+bool cpu_topology__smt_on(const struct cpu_topology *topology)
+{
+ for (u32 i = 0; i < topology->core_cpus_lists; i++) {
+ const char *cpu_list = topology->core_cpus_list[i];
+
+ /*
+ * If there is a need to separate siblings in a core then SMT is
+ * enabled.
+ */
+ if (strchr(cpu_list, ',') || strchr(cpu_list, '-'))
+ return true;
+ }
+ return false;
+}
+
static bool has_die_topology(void)
{
char filename[MAXPATHLEN];
diff --git a/tools/perf/util/cputopo.h b/tools/perf/util/cputopo.h
index 854e18f9041e..469db775a13c 100644
--- a/tools/perf/util/cputopo.h
+++ b/tools/perf/util/cputopo.h
@@ -58,6 +58,8 @@ struct hybrid_topology {

struct cpu_topology *cpu_topology__new(void);
void cpu_topology__delete(struct cpu_topology *tp);
+/* Determine from the core list whether SMT was enabled. */
+bool cpu_topology__smt_on(const struct cpu_topology *topology);

struct numa_topology *numa_topology__new(void);
void numa_topology__delete(struct numa_topology *tp);
diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
index 00bde682e743..8aa7dafa18b3 100644
--- a/tools/perf/util/expr.c
+++ b/tools/perf/util/expr.c
@@ -412,11 +412,6 @@ double expr__get_literal(const char *literal)
static struct cpu_topology *topology;
double result = NAN;

- if (!strcasecmp("#smt_on", literal)) {
- result = smt_on() > 0 ? 1.0 : 0.0;
- goto out;
- }
-
if (!strcmp("#num_cpus", literal)) {
result = cpu__max_present_cpu().cpu;
goto out;
@@ -440,6 +435,10 @@ double expr__get_literal(const char *literal)
goto out;
}
}
+ if (!strcasecmp("#smt_on", literal)) {
+ result = smt_on(topology) ? 1.0 : 0.0;
+ goto out;
+ }
if (!strcmp("#num_packages", literal)) {
result = topology->package_cpus_lists;
goto out;
diff --git a/tools/perf/util/smt.c b/tools/perf/util/smt.c
index 8fed03283c85..ce90c4ee4138 100644
--- a/tools/perf/util/smt.c
+++ b/tools/perf/util/smt.c
@@ -1,100 +1,23 @@
// SPDX-License-Identifier: GPL-2.0-only
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <linux/bitops.h>
+#include <string.h>
#include "api/fs/fs.h"
+#include "cputopo.h"
#include "smt.h"

-/**
- * hweight_str - Returns the number of bits set in str. Stops at first non-hex
- * or ',' character.
- */
-static int hweight_str(char *str)
-{
- int result = 0;
-
- while (*str) {
- switch (*str++) {
- case '0':
- case ',':
- break;
- case '1':
- case '2':
- case '4':
- case '8':
- result++;
- break;
- case '3':
- case '5':
- case '6':
- case '9':
- case 'a':
- case 'A':
- case 'c':
- case 'C':
- result += 2;
- break;
- case '7':
- case 'b':
- case 'B':
- case 'd':
- case 'D':
- case 'e':
- case 'E':
- result += 3;
- break;
- case 'f':
- case 'F':
- result += 4;
- break;
- default:
- goto done;
- }
- }
-done:
- return result;
-}
-
-int smt_on(void)
+bool smt_on(const struct cpu_topology *topology)
{
static bool cached;
- static int cached_result;
- int cpu;
- int ncpu;
+ static bool cached_result;
+ int fs_value;

if (cached)
return cached_result;

- if (sysfs__read_int("devices/system/cpu/smt/active", &cached_result) >= 0) {
- cached = true;
- return cached_result;
- }
-
- cached_result = 0;
- ncpu = sysconf(_SC_NPROCESSORS_CONF);
- for (cpu = 0; cpu < ncpu; cpu++) {
- unsigned long long siblings;
- char *str;
- size_t strlen;
- char fn[256];
+ if (sysfs__read_int("devices/system/cpu/smt/active", &fs_value) >= 0)
+ cached_result = (fs_value == 1);
+ else
+ cached_result = cpu_topology__smt_on(topology);

- snprintf(fn, sizeof fn,
- "devices/system/cpu/cpu%d/topology/thread_siblings", cpu);
- if (sysfs__read_str(fn, &str, &strlen) < 0) {
- snprintf(fn, sizeof fn,
- "devices/system/cpu/cpu%d/topology/core_cpus", cpu);
- if (sysfs__read_str(fn, &str, &strlen) < 0)
- continue;
- }
- /* Entry is hex, but does not have 0x, so need custom parser */
- siblings = hweight_str(str);
- free(str);
- if (siblings > 1) {
- cached_result = 1;
- break;
- }
- }
cached = true;
return cached_result;
}
diff --git a/tools/perf/util/smt.h b/tools/perf/util/smt.h
index a98d65808f6a..e26999c6b8d4 100644
--- a/tools/perf/util/smt.h
+++ b/tools/perf/util/smt.h
@@ -2,6 +2,9 @@
#ifndef __SMT_H
#define __SMT_H 1

-int smt_on(void);
+struct cpu_topology;
+
+/* Returns true if SMT (aka hyperthreading) is enabled. */
+bool smt_on(const struct cpu_topology *topology);

#endif /* __SMT_H */
--
2.37.2.672.g94769d06f0-goog

2022-08-30 16:52:30

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v1 6/8] perf stat: Delay metric parsing

Having metric parsing as part of argument processing causes issues as
flags like metric-no-group may be specified later. It also denies the
opportunity to optimize the events on SMT systems where fewer events
may be possible if we know the target is system-wide. Move metric
parsing to after command line option parsing. Because of how stat runs
this moves the parsing after record/report which fail to work with
metrics currently anyway.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/builtin-stat.c | 48 ++++++++++++++++++++++++-----------
tools/perf/util/metricgroup.c | 3 +--
tools/perf/util/metricgroup.h | 2 +-
3 files changed, 35 insertions(+), 18 deletions(-)

diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
index 7fb81a44672d..c813b1aa7d7c 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -191,6 +191,7 @@ static bool append_file;
static bool interval_count;
static const char *output_name;
static int output_fd;
+static char *metrics;

struct perf_stat {
bool record;
@@ -1147,14 +1148,21 @@ static int enable_metric_only(const struct option *opt __maybe_unused,
return 0;
}

-static int parse_metric_groups(const struct option *opt,
+static int append_metric_groups(const struct option *opt __maybe_unused,
const char *str,
int unset __maybe_unused)
{
- return metricgroup__parse_groups(opt, str,
- stat_config.metric_no_group,
- stat_config.metric_no_merge,
- &stat_config.metric_events);
+ if (metrics) {
+ char *tmp;
+
+ if (asprintf(&tmp, "%s,%s", metrics, str) < 0)
+ return -ENOMEM;
+ free(metrics);
+ metrics = tmp;
+ } else {
+ metrics = strdup(str);
+ }
+ return 0;
}

static int parse_control_option(const struct option *opt,
@@ -1298,7 +1306,7 @@ static struct option stat_options[] = {
"measure SMI cost"),
OPT_CALLBACK('M', "metrics", &evsel_list, "metric/metric group list",
"monitor specified metrics or metric groups (separated by ,)",
- parse_metric_groups),
+ append_metric_groups),
OPT_BOOLEAN_FLAG(0, "all-kernel", &stat_config.all_kernel,
"Configure all used events to run in kernel space.",
PARSE_OPT_EXCLUSIVE),
@@ -1791,11 +1799,9 @@ static int add_default_attributes(void)
* on an architecture test for such a metric name.
*/
if (metricgroup__has_metric("transaction")) {
- struct option opt = { .value = &evsel_list };
-
- return metricgroup__parse_groups(&opt, "transaction",
+ return metricgroup__parse_groups(evsel_list, "transaction",
stat_config.metric_no_group,
- stat_config.metric_no_merge,
+ stat_config.metric_no_merge,
&stat_config.metric_events);
}

@@ -2260,8 +2266,6 @@ int cmd_stat(int argc, const char **argv)
argc = parse_options_subcommand(argc, argv, stat_options, stat_subcommands,
(const char **) stat_usage,
PARSE_OPT_STOP_AT_NON_OPTION);
- perf_stat__collect_metric_expr(evsel_list);
- perf_stat__init_shadow_stats();

if (stat_config.csv_sep) {
stat_config.csv_output = true;
@@ -2428,6 +2432,23 @@ int cmd_stat(int argc, const char **argv)
target.system_wide = true;
}

+ if ((stat_config.aggr_mode == AGGR_THREAD) && (target.system_wide))
+ target.per_thread = true;
+
+ /*
+ * Metric parsing needs to be delayed as metrics may optimize events
+ * knowing the target is system-wide.
+ */
+ if (metrics) {
+ metricgroup__parse_groups(evsel_list, metrics,
+ stat_config.metric_no_group,
+ stat_config.metric_no_merge,
+ &stat_config.metric_events);
+ zfree(&metrics);
+ }
+ perf_stat__collect_metric_expr(evsel_list);
+ perf_stat__init_shadow_stats();
+
if (add_default_attributes())
goto out;

@@ -2447,9 +2468,6 @@ int cmd_stat(int argc, const char **argv)
}
}

- if ((stat_config.aggr_mode == AGGR_THREAD) && (target.system_wide))
- target.per_thread = true;
-
if (evlist__fix_hybrid_cpus(evsel_list, target.cpu_list)) {
pr_err("failed to use cpu list %s\n", target.cpu_list);
goto out;
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index b144c3e35264..9151346a16ab 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -1646,13 +1646,12 @@ static int parse_groups(struct evlist *perf_evlist, const char *str,
return ret;
}

-int metricgroup__parse_groups(const struct option *opt,
+int metricgroup__parse_groups(struct evlist *perf_evlist,
const char *str,
bool metric_no_group,
bool metric_no_merge,
struct rblist *metric_events)
{
- struct evlist *perf_evlist = *(struct evlist **)opt->value;
const struct pmu_events_table *table = pmu_events_table__find();

if (!table)
diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h
index 016b3b1a289a..af9ceadaec0f 100644
--- a/tools/perf/util/metricgroup.h
+++ b/tools/perf/util/metricgroup.h
@@ -64,7 +64,7 @@ struct metric_expr {
struct metric_event *metricgroup__lookup(struct rblist *metric_events,
struct evsel *evsel,
bool create);
-int metricgroup__parse_groups(const struct option *opt,
+int metricgroup__parse_groups(struct evlist *perf_evlist,
const char *str,
bool metric_no_group,
bool metric_no_merge,
--
2.37.2.672.g94769d06f0-goog

2022-08-30 16:59:42

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v1 5/8] perf topology: Add core_wide

It is possible to optimize metrics when all SMT threads (CPUs) on a
core are measuring events in system wide mode. For example, TMA
metrics defines CORE_CLKS for Sandybrdige as:

if SMT is disabled:
CPU_CLK_UNHALTED.THREAD
if SMT is enabled and recording on all SMT threads:
CPU_CLK_UNHALTED.THREAD_ANY / 2
if SMT is enabled and not recording on all SMT threads:
(CPU_CLK_UNHALTED.THREAD/2)*
(1+CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE/CPU_CLK_UNHALTED.REF_XCLK )

That is two more events are necessary when not gathering counts on all
SMT threads. To distinguish all SMT threads on a core vs system wide
(all CPUs) call the new property core wide. Add a core wide test that
determines the property from user requested CPUs, the topology and
system wide. System wide is required as other processes running on a
SMT thread will change the counts.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/util/cputopo.c | 46 +++++++++++++++++++++++++++++++++++++++
tools/perf/util/cputopo.h | 3 +++
tools/perf/util/smt.c | 14 ++++++++++++
tools/perf/util/smt.h | 7 ++++++
4 files changed, 70 insertions(+)

diff --git a/tools/perf/util/cputopo.c b/tools/perf/util/cputopo.c
index 511002e52714..1a3ff6449158 100644
--- a/tools/perf/util/cputopo.c
+++ b/tools/perf/util/cputopo.c
@@ -172,6 +172,52 @@ bool cpu_topology__smt_on(const struct cpu_topology *topology)
return false;
}

+bool cpu_topology__core_wide(const struct cpu_topology *topology,
+ const char *user_requested_cpu_list)
+{
+ struct perf_cpu_map *user_requested_cpus;
+
+ /*
+ * If user_requested_cpu_list is empty then all CPUs are recorded and so
+ * core_wide is true.
+ */
+ if (!user_requested_cpu_list)
+ return true;
+
+ user_requested_cpus = perf_cpu_map__new(user_requested_cpu_list);
+ /* Check that every user requested CPU is the complete set of SMT threads on a core. */
+ for (u32 i = 0; i < topology->core_cpus_lists; i++) {
+ const char *core_cpu_list = topology->core_cpus_list[i];
+ struct perf_cpu_map *core_cpus = perf_cpu_map__new(core_cpu_list);
+ struct perf_cpu cpu;
+ int idx;
+ bool has_first, first = true;
+
+ perf_cpu_map__for_each_cpu(cpu, idx, core_cpus) {
+ if (first) {
+ has_first = perf_cpu_map__has(user_requested_cpus, cpu);
+ first = false;
+ } else {
+ /*
+ * If the first core CPU is user requested then
+ * all subsequent CPUs in the core must be user
+ * requested too. If the first CPU isn't user
+ * requested then none of the others must be
+ * too.
+ */
+ if (perf_cpu_map__has(user_requested_cpus, cpu) != has_first) {
+ perf_cpu_map__put(core_cpus);
+ perf_cpu_map__put(user_requested_cpus);
+ return false;
+ }
+ }
+ }
+ perf_cpu_map__put(core_cpus);
+ }
+ perf_cpu_map__put(user_requested_cpus);
+ return true;
+}
+
static bool has_die_topology(void)
{
char filename[MAXPATHLEN];
diff --git a/tools/perf/util/cputopo.h b/tools/perf/util/cputopo.h
index 469db775a13c..969e5920a00e 100644
--- a/tools/perf/util/cputopo.h
+++ b/tools/perf/util/cputopo.h
@@ -60,6 +60,9 @@ struct cpu_topology *cpu_topology__new(void);
void cpu_topology__delete(struct cpu_topology *tp);
/* Determine from the core list whether SMT was enabled. */
bool cpu_topology__smt_on(const struct cpu_topology *topology);
+/* Are the sets of SMT siblings all enabled or all disabled in user_requested_cpus. */
+bool cpu_topology__core_wide(const struct cpu_topology *topology,
+ const char *user_requested_cpu_list);

struct numa_topology *numa_topology__new(void);
void numa_topology__delete(struct numa_topology *tp);
diff --git a/tools/perf/util/smt.c b/tools/perf/util/smt.c
index ce90c4ee4138..994e9e418227 100644
--- a/tools/perf/util/smt.c
+++ b/tools/perf/util/smt.c
@@ -21,3 +21,17 @@ bool smt_on(const struct cpu_topology *topology)
cached = true;
return cached_result;
}
+
+bool core_wide(bool system_wide, const char *user_requested_cpu_list,
+ const struct cpu_topology *topology)
+{
+ /* If not everything running on a core is being recorded then we can't use core_wide. */
+ if (!system_wide)
+ return false;
+
+ /* Cheap case that SMT is disabled and therefore we're inherently core_wide. */
+ if (!smt_on(topology))
+ return true;
+
+ return cpu_topology__core_wide(topology, user_requested_cpu_list);
+}
diff --git a/tools/perf/util/smt.h b/tools/perf/util/smt.h
index e26999c6b8d4..ae9095f2c38c 100644
--- a/tools/perf/util/smt.h
+++ b/tools/perf/util/smt.h
@@ -7,4 +7,11 @@ struct cpu_topology;
/* Returns true if SMT (aka hyperthreading) is enabled. */
bool smt_on(const struct cpu_topology *topology);

+/*
+ * Returns true when system wide and all SMT threads for a core are in the
+ * user_requested_cpus map.
+ */
+bool core_wide(bool system_wide, const char *user_requested_cpu_list,
+ const struct cpu_topology *topology);
+
#endif /* __SMT_H */
--
2.37.2.672.g94769d06f0-goog

2022-08-30 17:01:16

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v1 3/8] perf expr: Move the scanner_ctx into the parse_ctx

We currently maintain the two independently and copy from one to the
other. This is a burden when additional scanner context values are
necessary, so combine them.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/tests/expr.c | 2 +-
tools/perf/util/expr.c | 7 ++-----
tools/perf/util/expr.h | 10 +++++-----
tools/perf/util/metricgroup.c | 4 ++--
tools/perf/util/stat-shadow.c | 2 +-
5 files changed, 11 insertions(+), 14 deletions(-)

diff --git a/tools/perf/tests/expr.c b/tools/perf/tests/expr.c
index 2efe9e3a63b8..7ca5e37de560 100644
--- a/tools/perf/tests/expr.c
+++ b/tools/perf/tests/expr.c
@@ -133,7 +133,7 @@ static int test__expr(struct test_suite *t __maybe_unused, int subtest __maybe_u
(void **)&val_ptr));

expr__ctx_clear(ctx);
- ctx->runtime = 3;
+ ctx->sctx.runtime = 3;
TEST_ASSERT_VAL("find ids",
expr__find_ids("EVENT1\\,param\\=?@ + EVENT2\\,param\\=?@",
NULL, ctx) == 0);
diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
index c15a9852fa41..00bde682e743 100644
--- a/tools/perf/util/expr.c
+++ b/tools/perf/util/expr.c
@@ -310,7 +310,7 @@ struct expr_parse_ctx *expr__ctx_new(void)
free(ctx);
return NULL;
}
- ctx->runtime = 0;
+ ctx->sctx.runtime = 0;

return ctx;
}
@@ -344,16 +344,13 @@ static int
__expr__parse(double *val, struct expr_parse_ctx *ctx, const char *expr,
bool compute_ids)
{
- struct expr_scanner_ctx scanner_ctx = {
- .runtime = ctx->runtime,
- };
YY_BUFFER_STATE buffer;
void *scanner;
int ret;

pr_debug2("parsing metric: %s\n", expr);

- ret = expr_lex_init_extra(&scanner_ctx, &scanner);
+ ret = expr_lex_init_extra(&ctx->sctx, &scanner);
if (ret)
return ret;

diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
index bd2116983bbb..de9b886ec49a 100644
--- a/tools/perf/util/expr.h
+++ b/tools/perf/util/expr.h
@@ -13,17 +13,17 @@

struct metric_ref;

+struct expr_scanner_ctx {
+ int runtime;
+};
+
struct expr_parse_ctx {
struct hashmap *ids;
- int runtime;
+ struct expr_scanner_ctx sctx;
};

struct expr_id_data;

-struct expr_scanner_ctx {
- int runtime;
-};
-
struct hashmap *ids__new(void);
void ids__free(struct hashmap *ids);
int ids__insert(struct hashmap *ids, const char *id);
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 18aae040d61d..b144c3e35264 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -215,7 +215,7 @@ static struct metric *metric__new(const struct pmu_event *pe,
}
m->metric_expr = pe->metric_expr;
m->metric_unit = pe->unit;
- m->pctx->runtime = runtime;
+ m->pctx->sctx.runtime = runtime;
m->has_constraint = metric_no_group || metricgroup__has_constraint(pe);
m->metric_refs = NULL;
m->evlist = NULL;
@@ -1626,7 +1626,7 @@ static int parse_groups(struct evlist *perf_evlist, const char *str,
}
expr->metric_unit = m->metric_unit;
expr->metric_events = metric_events;
- expr->runtime = m->pctx->runtime;
+ expr->runtime = m->pctx->sctx.runtime;
list_add(&expr->nd, &me->head);
}

diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c
index 979c8cb918f7..1439acd109db 100644
--- a/tools/perf/util/stat-shadow.c
+++ b/tools/perf/util/stat-shadow.c
@@ -911,7 +911,7 @@ static void generic_metric(struct perf_stat_config *config,
if (!pctx)
return;

- pctx->runtime = runtime;
+ pctx->sctx.runtime = runtime;
i = prepare_metric(metric_events, metric_refs, pctx, cpu_map_idx, st);
if (i < 0) {
expr__ctx_free(pctx);
--
2.37.2.672.g94769d06f0-goog

2022-08-30 17:02:50

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v1 1/8] perf smt: Tidy header guard add SPDX

Make the header guard consistent with others.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/util/smt.c | 1 +
tools/perf/util/smt.h | 7 ++++---
2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/tools/perf/util/smt.c b/tools/perf/util/smt.c
index 2b0a36ebf27a..8fed03283c85 100644
--- a/tools/perf/util/smt.c
+++ b/tools/perf/util/smt.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
diff --git a/tools/perf/util/smt.h b/tools/perf/util/smt.h
index b8414b7bcbc8..a98d65808f6a 100644
--- a/tools/perf/util/smt.h
+++ b/tools/perf/util/smt.h
@@ -1,6 +1,7 @@
-#ifndef SMT_H
-#define SMT_H 1
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __SMT_H
+#define __SMT_H 1

int smt_on(void);

-#endif
+#endif /* __SMT_H */
--
2.37.2.672.g94769d06f0-goog

2022-08-30 17:14:20

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v1 7/8] perf metrics: Wire up core_wide

Pass state necessary for core_wide into the expression parser. Add
system_wide and user_requested_cpu_list to perf_stat_config to make it
available at display time. evlist isn't used as the
evlist__create_maps, that computes user_requested_cpus, needs the list
of events which is generated by the metric.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/builtin-stat.c | 9 ++++
tools/perf/util/expr.c | 10 ++++-
tools/perf/util/expr.h | 4 +-
tools/perf/util/expr.l | 6 +--
tools/perf/util/metricgroup.c | 82 +++++++++++++++++++++++++++--------
tools/perf/util/metricgroup.h | 2 +
tools/perf/util/stat-shadow.c | 11 +++--
tools/perf/util/stat.h | 2 +
8 files changed, 101 insertions(+), 25 deletions(-)

diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
index c813b1aa7d7c..0554ba6547a5 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -1802,6 +1802,8 @@ static int add_default_attributes(void)
return metricgroup__parse_groups(evsel_list, "transaction",
stat_config.metric_no_group,
stat_config.metric_no_merge,
+ stat_config.user_requested_cpu_list,
+ stat_config.system_wide,
&stat_config.metric_events);
}

@@ -2435,6 +2437,10 @@ int cmd_stat(int argc, const char **argv)
if ((stat_config.aggr_mode == AGGR_THREAD) && (target.system_wide))
target.per_thread = true;

+ stat_config.system_wide = target.system_wide;
+ if (target.cpu_list)
+ stat_config.user_requested_cpu_list = strdup(target.cpu_list);
+
/*
* Metric parsing needs to be delayed as metrics may optimize events
* knowing the target is system-wide.
@@ -2443,6 +2449,8 @@ int cmd_stat(int argc, const char **argv)
metricgroup__parse_groups(evsel_list, metrics,
stat_config.metric_no_group,
stat_config.metric_no_merge,
+ stat_config.user_requested_cpu_list,
+ stat_config.system_wide,
&stat_config.metric_events);
zfree(&metrics);
}
@@ -2633,6 +2641,7 @@ int cmd_stat(int argc, const char **argv)
iostat_release(evsel_list);

zfree(&stat_config.walltime_run);
+ zfree(&stat_config.user_requested_cpu_list);

if (smi_cost && smi_reset)
sysfs__write_int(FREEZE_ON_SMI_PATH, 0);
diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
index 8aa7dafa18b3..ce186bf663c4 100644
--- a/tools/perf/util/expr.c
+++ b/tools/perf/util/expr.c
@@ -310,7 +310,9 @@ struct expr_parse_ctx *expr__ctx_new(void)
free(ctx);
return NULL;
}
+ ctx->sctx.user_requested_cpu_list = NULL;
ctx->sctx.runtime = 0;
+ ctx->sctx.system_wide = false;

return ctx;
}
@@ -332,6 +334,7 @@ void expr__ctx_free(struct expr_parse_ctx *ctx)
struct hashmap_entry *cur;
size_t bkt;

+ free(ctx->sctx.user_requested_cpu_list);
hashmap__for_each_entry(ctx->ids, cur, bkt) {
free((char *)cur->key);
free(cur->value);
@@ -407,7 +410,7 @@ double arch_get_tsc_freq(void)
}
#endif

-double expr__get_literal(const char *literal)
+double expr__get_literal(const char *literal, const struct expr_scanner_ctx *ctx)
{
static struct cpu_topology *topology;
double result = NAN;
@@ -439,6 +442,11 @@ double expr__get_literal(const char *literal)
result = smt_on(topology) ? 1.0 : 0.0;
goto out;
}
+ if (!strcmp("#core_wide", literal)) {
+ result = core_wide(ctx->system_wide, ctx->user_requested_cpu_list, topology)
+ ? 1.0 : 0.0;
+ goto out;
+ }
if (!strcmp("#num_packages", literal)) {
result = topology->package_cpus_lists;
goto out;
diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
index de9b886ec49a..32740e4c81ef 100644
--- a/tools/perf/util/expr.h
+++ b/tools/perf/util/expr.h
@@ -14,7 +14,9 @@
struct metric_ref;

struct expr_scanner_ctx {
+ char *user_requested_cpu_list;
int runtime;
+ bool system_wide;
};

struct expr_parse_ctx {
@@ -58,6 +60,6 @@ int expr__find_ids(const char *expr, const char *one,

double expr_id_data__value(const struct expr_id_data *data);
double expr_id_data__source_count(const struct expr_id_data *data);
-double expr__get_literal(const char *literal);
+double expr__get_literal(const char *literal, const struct expr_scanner_ctx *ctx);

#endif
diff --git a/tools/perf/util/expr.l b/tools/perf/util/expr.l
index 4dc8edbfd9ce..0168a9637330 100644
--- a/tools/perf/util/expr.l
+++ b/tools/perf/util/expr.l
@@ -79,11 +79,11 @@ static int str(yyscan_t scanner, int token, int runtime)
return token;
}

-static int literal(yyscan_t scanner)
+static int literal(yyscan_t scanner, const struct expr_scanner_ctx *sctx)
{
YYSTYPE *yylval = expr_get_lval(scanner);

- yylval->num = expr__get_literal(expr_get_text(scanner));
+ yylval->num = expr__get_literal(expr_get_text(scanner), sctx);
if (isnan(yylval->num))
return EXPR_ERROR;

@@ -108,7 +108,7 @@ min { return MIN; }
if { return IF; }
else { return ELSE; }
source_count { return SOURCE_COUNT; }
-{literal} { return literal(yyscanner); }
+{literal} { return literal(yyscanner, sctx); }
{number} { return value(yyscanner); }
{symbol} { return str(yyscanner, ID, sctx->runtime); }
"|" { return '|'; }
diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index 9151346a16ab..f7d93dc02326 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -22,6 +22,7 @@
#include <linux/list_sort.h>
#include <linux/string.h>
#include <linux/zalloc.h>
+#include <perf/cpumap.h>
#include <subcmd/parse-options.h>
#include <api/fs/fs.h>
#include "util.h"
@@ -192,7 +193,9 @@ static bool metricgroup__has_constraint(const struct pmu_event *pe)
static struct metric *metric__new(const struct pmu_event *pe,
const char *modifier,
bool metric_no_group,
- int runtime)
+ int runtime,
+ const char *user_requested_cpu_list,
+ bool system_wide)
{
struct metric *m;

@@ -215,7 +218,11 @@ static struct metric *metric__new(const struct pmu_event *pe,
}
m->metric_expr = pe->metric_expr;
m->metric_unit = pe->unit;
+ m->pctx->sctx.user_requested_cpu_list = NULL;
+ if (user_requested_cpu_list)
+ m->pctx->sctx.user_requested_cpu_list = strdup(user_requested_cpu_list);
m->pctx->sctx.runtime = runtime;
+ m->pctx->sctx.system_wide = system_wide;
m->has_constraint = metric_no_group || metricgroup__has_constraint(pe);
m->metric_refs = NULL;
m->evlist = NULL;
@@ -874,6 +881,8 @@ struct metricgroup_add_iter_data {
int *ret;
bool *has_match;
bool metric_no_group;
+ const char *user_requested_cpu_list;
+ bool system_wide;
struct metric *root_metric;
const struct visited_metric *visited;
const struct pmu_events_table *table;
@@ -887,6 +896,8 @@ static int add_metric(struct list_head *metric_list,
const struct pmu_event *pe,
const char *modifier,
bool metric_no_group,
+ const char *user_requested_cpu_list,
+ bool system_wide,
struct metric *root_metric,
const struct visited_metric *visited,
const struct pmu_events_table *table);
@@ -899,6 +910,8 @@ static int add_metric(struct list_head *metric_list,
* @metric_no_group: Should events written to events be grouped "{}" or
* global. Grouping is the default but due to multiplexing the
* user may override.
+ * @user_requested_cpu_list: Command line specified CPUs to record on.
+ * @system_wide: Are events for all processes recorded.
* @root_metric: Metrics may reference other metrics to form a tree. In this
* case the root_metric holds all the IDs and a list of referenced
* metrics. When adding a root this argument is NULL.
@@ -910,6 +923,8 @@ static int add_metric(struct list_head *metric_list,
static int resolve_metric(struct list_head *metric_list,
const char *modifier,
bool metric_no_group,
+ const char *user_requested_cpu_list,
+ bool system_wide,
struct metric *root_metric,
const struct visited_metric *visited,
const struct pmu_events_table *table)
@@ -956,7 +971,8 @@ static int resolve_metric(struct list_head *metric_list,
*/
for (i = 0; i < pending_cnt; i++) {
ret = add_metric(metric_list, &pending[i].pe, modifier, metric_no_group,
- root_metric, visited, table);
+ user_requested_cpu_list, system_wide, root_metric, visited,
+ table);
if (ret)
break;
}
@@ -974,6 +990,8 @@ static int resolve_metric(struct list_head *metric_list,
* global. Grouping is the default but due to multiplexing the
* user may override.
* @runtime: A special argument for the parser only known at runtime.
+ * @user_requested_cpu_list: Command line specified CPUs to record on.
+ * @system_wide: Are events for all processes recorded.
* @root_metric: Metrics may reference other metrics to form a tree. In this
* case the root_metric holds all the IDs and a list of referenced
* metrics. When adding a root this argument is NULL.
@@ -987,6 +1005,8 @@ static int __add_metric(struct list_head *metric_list,
const char *modifier,
bool metric_no_group,
int runtime,
+ const char *user_requested_cpu_list,
+ bool system_wide,
struct metric *root_metric,
const struct visited_metric *visited,
const struct pmu_events_table *table)
@@ -1011,7 +1031,8 @@ static int __add_metric(struct list_head *metric_list,
* This metric is the root of a tree and may reference other
* metrics that are added recursively.
*/
- root_metric = metric__new(pe, modifier, metric_no_group, runtime);
+ root_metric = metric__new(pe, modifier, metric_no_group, runtime,
+ user_requested_cpu_list, system_wide);
if (!root_metric)
return -ENOMEM;

@@ -1060,8 +1081,9 @@ static int __add_metric(struct list_head *metric_list,
ret = -EINVAL;
} else {
/* Resolve referenced metrics. */
- ret = resolve_metric(metric_list, modifier, metric_no_group, root_metric,
- &visited_node, table);
+ ret = resolve_metric(metric_list, modifier, metric_no_group,
+ user_requested_cpu_list, system_wide,
+ root_metric, &visited_node, table);
}

if (ret) {
@@ -1109,6 +1131,8 @@ static int add_metric(struct list_head *metric_list,
const struct pmu_event *pe,
const char *modifier,
bool metric_no_group,
+ const char *user_requested_cpu_list,
+ bool system_wide,
struct metric *root_metric,
const struct visited_metric *visited,
const struct pmu_events_table *table)
@@ -1119,7 +1143,8 @@ static int add_metric(struct list_head *metric_list,

if (!strstr(pe->metric_expr, "?")) {
ret = __add_metric(metric_list, pe, modifier, metric_no_group, 0,
- root_metric, visited, table);
+ user_requested_cpu_list, system_wide, root_metric,
+ visited, table);
} else {
int j, count;

@@ -1132,7 +1157,8 @@ static int add_metric(struct list_head *metric_list,

for (j = 0; j < count && !ret; j++)
ret = __add_metric(metric_list, pe, modifier, metric_no_group, j,
- root_metric, visited, table);
+ user_requested_cpu_list, system_wide,
+ root_metric, visited, table);
}

return ret;
@@ -1149,6 +1175,7 @@ static int metricgroup__add_metric_sys_event_iter(const struct pmu_event *pe,
return 0;

ret = add_metric(d->metric_list, pe, d->modifier, d->metric_no_group,
+ d->user_requested_cpu_list, d->system_wide,
d->root_metric, d->visited, d->table);
if (ret)
goto out;
@@ -1191,7 +1218,9 @@ struct metricgroup__add_metric_data {
struct list_head *list;
const char *metric_name;
const char *modifier;
+ const char *user_requested_cpu_list;
bool metric_no_group;
+ bool system_wide;
bool has_match;
};

@@ -1208,8 +1237,8 @@ static int metricgroup__add_metric_callback(const struct pmu_event *pe,

data->has_match = true;
ret = add_metric(data->list, pe, data->modifier, data->metric_no_group,
- /*root_metric=*/NULL,
- /*visited_metrics=*/NULL, table);
+ data->user_requested_cpu_list, data->system_wide,
+ /*root_metric=*/NULL, /*visited_metrics=*/NULL, table);
}
return ret;
}
@@ -1223,12 +1252,16 @@ static int metricgroup__add_metric_callback(const struct pmu_event *pe,
* @metric_no_group: Should events written to events be grouped "{}" or
* global. Grouping is the default but due to multiplexing the
* user may override.
+ * @user_requested_cpu_list: Command line specified CPUs to record on.
+ * @system_wide: Are events for all processes recorded.
* @metric_list: The list that the metric or metric group are added to.
* @table: The table that is searched for metrics, most commonly the table for the
* architecture perf is running upon.
*/
static int metricgroup__add_metric(const char *metric_name, const char *modifier,
bool metric_no_group,
+ const char *user_requested_cpu_list,
+ bool system_wide,
struct list_head *metric_list,
const struct pmu_events_table *table)
{
@@ -1242,6 +1275,8 @@ static int metricgroup__add_metric(const char *metric_name, const char *modifier
.metric_name = metric_name,
.modifier = modifier,
.metric_no_group = metric_no_group,
+ .user_requested_cpu_list = user_requested_cpu_list,
+ .system_wide = system_wide,
.has_match = false,
};
/*
@@ -1263,6 +1298,8 @@ static int metricgroup__add_metric(const char *metric_name, const char *modifier
.metric_name = metric_name,
.modifier = modifier,
.metric_no_group = metric_no_group,
+ .user_requested_cpu_list = user_requested_cpu_list,
+ .system_wide = system_wide,
.has_match = &has_match,
.ret = &ret,
.table = table,
@@ -1293,12 +1330,15 @@ static int metricgroup__add_metric(const char *metric_name, const char *modifier
* @metric_no_group: Should events written to events be grouped "{}" or
* global. Grouping is the default but due to multiplexing the
* user may override.
+ * @user_requested_cpu_list: Command line specified CPUs to record on.
+ * @system_wide: Are events for all processes recorded.
* @metric_list: The list that metrics are added to.
* @table: The table that is searched for metrics, most commonly the table for the
* architecture perf is running upon.
*/
static int metricgroup__add_metric_list(const char *list, bool metric_no_group,
- struct list_head *metric_list,
+ const char *user_requested_cpu_list,
+ bool system_wide, struct list_head *metric_list,
const struct pmu_events_table *table)
{
char *list_itr, *list_copy, *metric_name, *modifier;
@@ -1315,8 +1355,8 @@ static int metricgroup__add_metric_list(const char *list, bool metric_no_group,
*modifier++ = '\0';

ret = metricgroup__add_metric(metric_name, modifier,
- metric_no_group, metric_list,
- table);
+ metric_no_group, user_requested_cpu_list,
+ system_wide, metric_list, table);
if (ret == -EINVAL)
pr_err("Cannot find metric or group `%s'\n", metric_name);

@@ -1505,6 +1545,8 @@ static int parse_ids(bool metric_no_merge, struct perf_pmu *fake_pmu,
static int parse_groups(struct evlist *perf_evlist, const char *str,
bool metric_no_group,
bool metric_no_merge,
+ const char *user_requested_cpu_list,
+ bool system_wide,
struct perf_pmu *fake_pmu,
struct rblist *metric_events_list,
const struct pmu_events_table *table)
@@ -1518,7 +1560,8 @@ static int parse_groups(struct evlist *perf_evlist, const char *str,
if (metric_events_list->nr_entries == 0)
metricgroup__rblist_init(metric_events_list);
ret = metricgroup__add_metric_list(str, metric_no_group,
- &metric_list, table);
+ user_requested_cpu_list,
+ system_wide, &metric_list, table);
if (ret)
goto out;

@@ -1650,6 +1693,8 @@ int metricgroup__parse_groups(struct evlist *perf_evlist,
const char *str,
bool metric_no_group,
bool metric_no_merge,
+ const char *user_requested_cpu_list,
+ bool system_wide,
struct rblist *metric_events)
{
const struct pmu_events_table *table = pmu_events_table__find();
@@ -1657,8 +1702,9 @@ int metricgroup__parse_groups(struct evlist *perf_evlist,
if (!table)
return -EINVAL;

- return parse_groups(perf_evlist, str, metric_no_group,
- metric_no_merge, NULL, metric_events, table);
+ return parse_groups(perf_evlist, str, metric_no_group, metric_no_merge,
+ user_requested_cpu_list, system_wide,
+ /*fake_pmu=*/NULL, metric_events, table);
}

int metricgroup__parse_groups_test(struct evlist *evlist,
@@ -1668,8 +1714,10 @@ int metricgroup__parse_groups_test(struct evlist *evlist,
bool metric_no_merge,
struct rblist *metric_events)
{
- return parse_groups(evlist, str, metric_no_group,
- metric_no_merge, &perf_pmu__fake, metric_events, table);
+ return parse_groups(evlist, str, metric_no_group, metric_no_merge,
+ /*user_requested_cpu_list=*/NULL,
+ /*system_wide=*/false,
+ &perf_pmu__fake, metric_events, table);
}

static int metricgroup__has_metric_callback(const struct pmu_event *pe,
diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h
index af9ceadaec0f..732d3a0d3334 100644
--- a/tools/perf/util/metricgroup.h
+++ b/tools/perf/util/metricgroup.h
@@ -68,6 +68,8 @@ int metricgroup__parse_groups(struct evlist *perf_evlist,
const char *str,
bool metric_no_group,
bool metric_no_merge,
+ const char *user_requested_cpu_list,
+ bool system_wide,
struct rblist *metric_events);
int metricgroup__parse_groups_test(struct evlist *evlist,
const struct pmu_events_table *table,
diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c
index 1439acd109db..1deb75741df4 100644
--- a/tools/perf/util/stat-shadow.c
+++ b/tools/perf/util/stat-shadow.c
@@ -911,7 +911,10 @@ static void generic_metric(struct perf_stat_config *config,
if (!pctx)
return;

+ if (config->user_requested_cpu_list)
+ pctx->sctx.user_requested_cpu_list = strdup(config->user_requested_cpu_list);
pctx->sctx.runtime = runtime;
+ pctx->sctx.system_wide = config->system_wide;
i = prepare_metric(metric_events, metric_refs, pctx, cpu_map_idx, st);
if (i < 0) {
expr__ctx_free(pctx);
@@ -1304,7 +1307,8 @@ void perf_stat__print_shadow_stats(struct perf_stat_config *config,
core_bound * 100.);
} else if (evsel->metric_expr) {
generic_metric(config, evsel->metric_expr, evsel->metric_events, NULL,
- evsel->name, evsel->metric_name, NULL, 1, cpu_map_idx, out, st);
+ evsel->name, evsel->metric_name, NULL, 1,
+ cpu_map_idx, out, st);
} else if (runtime_stat_n(st, STAT_NSECS, cpu_map_idx, &rsd) != 0) {
char unit = ' ';
char unit_buf[10] = "/sec";
@@ -1329,8 +1333,9 @@ void perf_stat__print_shadow_stats(struct perf_stat_config *config,
if (num++ > 0)
out->new_line(config, ctxp);
generic_metric(config, mexp->metric_expr, mexp->metric_events,
- mexp->metric_refs, evsel->name, mexp->metric_name,
- mexp->metric_unit, mexp->runtime, cpu_map_idx, out, st);
+ mexp->metric_refs, evsel->name, mexp->metric_name,
+ mexp->metric_unit, mexp->runtime,
+ cpu_map_idx, out, st);
}
}
if (num == 0)
diff --git a/tools/perf/util/stat.h b/tools/perf/util/stat.h
index 668250022f8c..72713b344b79 100644
--- a/tools/perf/util/stat.h
+++ b/tools/perf/util/stat.h
@@ -141,6 +141,8 @@ struct perf_stat_config {
bool stop_read_counter;
bool quiet;
bool iostat_run;
+ char *user_requested_cpu_list;
+ bool system_wide;
FILE *output;
unsigned int interval;
unsigned int timeout;
--
2.37.2.672.g94769d06f0-goog

2022-08-30 17:18:04

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v1 2/8] perf metric: Return early if no CPU PMU table exists

Previous behavior is to segfault if there is no CPU PMU table and a
metric is sought. To reproduce compile with NO_JEVENTS=1 then request
a metric, for example, "perf stat -M IPC true".

Fixes: 00facc760903 ("perf jevents: Switch build to use jevents.py")
Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/util/metricgroup.c | 3 +++
1 file changed, 3 insertions(+)

diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
index ad5cacdecd81..18aae040d61d 100644
--- a/tools/perf/util/metricgroup.c
+++ b/tools/perf/util/metricgroup.c
@@ -1655,6 +1655,9 @@ int metricgroup__parse_groups(const struct option *opt,
struct evlist *perf_evlist = *(struct evlist **)opt->value;
const struct pmu_events_table *table = pmu_events_table__find();

+ if (!table)
+ return -EINVAL;
+
return parse_groups(perf_evlist, str, metric_no_group,
metric_no_merge, NULL, metric_events, table);
}
--
2.37.2.672.g94769d06f0-goog

2022-08-30 17:19:05

by Ian Rogers

[permalink] [raw]
Subject: [PATCH v1 8/8] perf test: Add basic core_wide expression test

Add basic test for coverage, similar to #smt_on.

Signed-off-by: Ian Rogers <[email protected]>
---
tools/perf/tests/expr.c | 13 +++++++++++++
1 file changed, 13 insertions(+)

diff --git a/tools/perf/tests/expr.c b/tools/perf/tests/expr.c
index db736ed49556..8bd719766814 100644
--- a/tools/perf/tests/expr.c
+++ b/tools/perf/tests/expr.c
@@ -158,6 +158,9 @@ static int test__expr(struct test_suite *t __maybe_unused, int subtest __maybe_u
{
struct cpu_topology *topology = cpu_topology__new();
bool smton = smt_on(topology);
+ bool corewide = core_wide(/*system_wide=*/false,
+ /*user_requested_cpus=*/false,
+ topology);

cpu_topology__delete(topology);
expr__ctx_clear(ctx);
@@ -168,6 +171,16 @@ static int test__expr(struct test_suite *t __maybe_unused, int subtest __maybe_u
TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids,
smton ? "EVENT1" : "EVENT2",
(void **)&val_ptr));
+
+ expr__ctx_clear(ctx);
+ TEST_ASSERT_VAL("find ids",
+ expr__find_ids("EVENT1 if #core_wide else EVENT2",
+ NULL, ctx) == 0);
+ TEST_ASSERT_VAL("find ids", hashmap__size(ctx->ids) == 1);
+ TEST_ASSERT_VAL("find ids", hashmap__find(ctx->ids,
+ corewide ? "EVENT1" : "EVENT2",
+ (void **)&val_ptr));
+
}
/* The expression is a constant 1.0 without needing to evaluate EVENT1. */
expr__ctx_clear(ctx);
--
2.37.2.672.g94769d06f0-goog

2022-08-31 12:33:32

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH v1 2/8] perf metric: Return early if no CPU PMU table exists

Em Tue, Aug 30, 2022 at 09:48:40AM -0700, Ian Rogers escreveu:
> Previous behavior is to segfault if there is no CPU PMU table and a
> metric is sought. To reproduce compile with NO_JEVENTS=1 then request
> a metric, for example, "perf stat -M IPC true".
>
> Fixes: 00facc760903 ("perf jevents: Switch build to use jevents.py")
> Signed-off-by: Ian Rogers <[email protected]>

Before:

$ make -k NO_JEVENTS=1 BUILD_BPF_SKEL=1 O=/tmp/build/perf-urgent -C tools/perf install-bin
$ perf stat -M IPC true
Segmentation fault (core dumped)
$

After:

$ perf stat -M IPC true

Usage: perf stat [<options>] [<command>]

-M, --metrics <metric/metric group list>
monitor specified metrics or metric groups (separated by ,)
$

Better, but perhaps we could provide some better message as to why the
metric provided isn't acceptable?

Anyway, applied to perf/urgent.

Thanks,

- Arnaldo

> ---
> tools/perf/util/metricgroup.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
> index ad5cacdecd81..18aae040d61d 100644
> --- a/tools/perf/util/metricgroup.c
> +++ b/tools/perf/util/metricgroup.c
> @@ -1655,6 +1655,9 @@ int metricgroup__parse_groups(const struct option *opt,
> struct evlist *perf_evlist = *(struct evlist **)opt->value;
> const struct pmu_events_table *table = pmu_events_table__find();
>
> + if (!table)
> + return -EINVAL;
> +
> return parse_groups(perf_evlist, str, metric_no_group,
> metric_no_merge, NULL, metric_events, table);
> }
> --
> 2.37.2.672.g94769d06f0-goog

--

- Arnaldo

2022-08-31 13:18:50

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH v1 2/8] perf metric: Return early if no CPU PMU table exists

Em Tue, Aug 30, 2022 at 09:48:40AM -0700, Ian Rogers escreveu:
> Previous behavior is to segfault if there is no CPU PMU table and a
> metric is sought. To reproduce compile with NO_JEVENTS=1 then request
> a metric, for example, "perf stat -M IPC true".

Thanks, applied to perf/urgent.

- Arnaldo


> Fixes: 00facc760903 ("perf jevents: Switch build to use jevents.py")
> Signed-off-by: Ian Rogers <[email protected]>
> ---
> tools/perf/util/metricgroup.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
> index ad5cacdecd81..18aae040d61d 100644
> --- a/tools/perf/util/metricgroup.c
> +++ b/tools/perf/util/metricgroup.c
> @@ -1655,6 +1655,9 @@ int metricgroup__parse_groups(const struct option *opt,
> struct evlist *perf_evlist = *(struct evlist **)opt->value;
> const struct pmu_events_table *table = pmu_events_table__find();
>
> + if (!table)
> + return -EINVAL;
> +
> return parse_groups(perf_evlist, str, metric_no_group,
> metric_no_merge, NULL, metric_events, table);
> }
> --
> 2.37.2.672.g94769d06f0-goog

--

- Arnaldo

2022-08-31 14:57:01

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH v1 5/8] perf topology: Add core_wide

Em Tue, Aug 30, 2022 at 09:48:43AM -0700, Ian Rogers escreveu:
> It is possible to optimize metrics when all SMT threads (CPUs) on a
> core are measuring events in system wide mode. For example, TMA
> metrics defines CORE_CLKS for Sandybrdige as:
>
> if SMT is disabled:
> CPU_CLK_UNHALTED.THREAD
> if SMT is enabled and recording on all SMT threads:
> CPU_CLK_UNHALTED.THREAD_ANY / 2
> if SMT is enabled and not recording on all SMT threads:
> (CPU_CLK_UNHALTED.THREAD/2)*
> (1+CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE/CPU_CLK_UNHALTED.REF_XCLK )
>
> That is two more events are necessary when not gathering counts on all
> SMT threads. To distinguish all SMT threads on a core vs system wide
> (all CPUs) call the new property core wide. Add a core wide test that
> determines the property from user requested CPUs, the topology and
> system wide. System wide is required as other processes running on a
> SMT thread will change the counts.
>
> Signed-off-by: Ian Rogers <[email protected]>
> ---
> tools/perf/util/cputopo.c | 46 +++++++++++++++++++++++++++++++++++++++
> tools/perf/util/cputopo.h | 3 +++
> tools/perf/util/smt.c | 14 ++++++++++++
> tools/perf/util/smt.h | 7 ++++++
> 4 files changed, 70 insertions(+)
>
> diff --git a/tools/perf/util/cputopo.c b/tools/perf/util/cputopo.c
> index 511002e52714..1a3ff6449158 100644
> --- a/tools/perf/util/cputopo.c
> +++ b/tools/perf/util/cputopo.c
> @@ -172,6 +172,52 @@ bool cpu_topology__smt_on(const struct cpu_topology *topology)
> return false;
> }
>
> +bool cpu_topology__core_wide(const struct cpu_topology *topology,
> + const char *user_requested_cpu_list)
> +{
> + struct perf_cpu_map *user_requested_cpus;
> +
> + /*
> + * If user_requested_cpu_list is empty then all CPUs are recorded and so
> + * core_wide is true.
> + */
> + if (!user_requested_cpu_list)
> + return true;
> +
> + user_requested_cpus = perf_cpu_map__new(user_requested_cpu_list);

Don't we need a NULL test here?

> + /* Check that every user requested CPU is the complete set of SMT threads on a core. */
> + for (u32 i = 0; i < topology->core_cpus_lists; i++) {
> + const char *core_cpu_list = topology->core_cpus_list[i];
> + struct perf_cpu_map *core_cpus = perf_cpu_map__new(core_cpu_list);

Here too, no?

> + struct perf_cpu cpu;
> + int idx;
> + bool has_first, first = true;
> +
> + perf_cpu_map__for_each_cpu(cpu, idx, core_cpus) {
> + if (first) {
> + has_first = perf_cpu_map__has(user_requested_cpus, cpu);
> + first = false;
> + } else {
> + /*
> + * If the first core CPU is user requested then
> + * all subsequent CPUs in the core must be user
> + * requested too. If the first CPU isn't user
> + * requested then none of the others must be
> + * too.
> + */
> + if (perf_cpu_map__has(user_requested_cpus, cpu) != has_first) {
> + perf_cpu_map__put(core_cpus);
> + perf_cpu_map__put(user_requested_cpus);
> + return false;
> + }
> + }
> + }
> + perf_cpu_map__put(core_cpus);
> + }
> + perf_cpu_map__put(user_requested_cpus);
> + return true;
> +}
> +
> static bool has_die_topology(void)
> {
> char filename[MAXPATHLEN];
> diff --git a/tools/perf/util/cputopo.h b/tools/perf/util/cputopo.h
> index 469db775a13c..969e5920a00e 100644
> --- a/tools/perf/util/cputopo.h
> +++ b/tools/perf/util/cputopo.h
> @@ -60,6 +60,9 @@ struct cpu_topology *cpu_topology__new(void);
> void cpu_topology__delete(struct cpu_topology *tp);
> /* Determine from the core list whether SMT was enabled. */
> bool cpu_topology__smt_on(const struct cpu_topology *topology);
> +/* Are the sets of SMT siblings all enabled or all disabled in user_requested_cpus. */
> +bool cpu_topology__core_wide(const struct cpu_topology *topology,
> + const char *user_requested_cpu_list);
>
> struct numa_topology *numa_topology__new(void);
> void numa_topology__delete(struct numa_topology *tp);
> diff --git a/tools/perf/util/smt.c b/tools/perf/util/smt.c
> index ce90c4ee4138..994e9e418227 100644
> --- a/tools/perf/util/smt.c
> +++ b/tools/perf/util/smt.c
> @@ -21,3 +21,17 @@ bool smt_on(const struct cpu_topology *topology)
> cached = true;
> return cached_result;
> }
> +
> +bool core_wide(bool system_wide, const char *user_requested_cpu_list,
> + const struct cpu_topology *topology)
> +{
> + /* If not everything running on a core is being recorded then we can't use core_wide. */
> + if (!system_wide)
> + return false;
> +
> + /* Cheap case that SMT is disabled and therefore we're inherently core_wide. */
> + if (!smt_on(topology))
> + return true;
> +
> + return cpu_topology__core_wide(topology, user_requested_cpu_list);
> +}
> diff --git a/tools/perf/util/smt.h b/tools/perf/util/smt.h
> index e26999c6b8d4..ae9095f2c38c 100644
> --- a/tools/perf/util/smt.h
> +++ b/tools/perf/util/smt.h
> @@ -7,4 +7,11 @@ struct cpu_topology;
> /* Returns true if SMT (aka hyperthreading) is enabled. */
> bool smt_on(const struct cpu_topology *topology);
>
> +/*
> + * Returns true when system wide and all SMT threads for a core are in the
> + * user_requested_cpus map.
> + */
> +bool core_wide(bool system_wide, const char *user_requested_cpu_list,
> + const struct cpu_topology *topology);
> +
> #endif /* __SMT_H */
> --
> 2.37.2.672.g94769d06f0-goog

--

- Arnaldo

2022-08-31 14:58:22

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH v1 7/8] perf metrics: Wire up core_wide

Em Tue, Aug 30, 2022 at 09:48:45AM -0700, Ian Rogers escreveu:
> Pass state necessary for core_wide into the expression parser. Add
> system_wide and user_requested_cpu_list to perf_stat_config to make it
> available at display time. evlist isn't used as the
> evlist__create_maps, that computes user_requested_cpus, needs the list
> of events which is generated by the metric.
>
> Signed-off-by: Ian Rogers <[email protected]>
> ---
> tools/perf/builtin-stat.c | 9 ++++
> tools/perf/util/expr.c | 10 ++++-
> tools/perf/util/expr.h | 4 +-
> tools/perf/util/expr.l | 6 +--
> tools/perf/util/metricgroup.c | 82 +++++++++++++++++++++++++++--------
> tools/perf/util/metricgroup.h | 2 +
> tools/perf/util/stat-shadow.c | 11 +++--
> tools/perf/util/stat.h | 2 +
> 8 files changed, 101 insertions(+), 25 deletions(-)
>
> diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
> index c813b1aa7d7c..0554ba6547a5 100644
> --- a/tools/perf/builtin-stat.c
> +++ b/tools/perf/builtin-stat.c
> @@ -1802,6 +1802,8 @@ static int add_default_attributes(void)
> return metricgroup__parse_groups(evsel_list, "transaction",
> stat_config.metric_no_group,
> stat_config.metric_no_merge,
> + stat_config.user_requested_cpu_list,
> + stat_config.system_wide,
> &stat_config.metric_events);
> }
>
> @@ -2435,6 +2437,10 @@ int cmd_stat(int argc, const char **argv)
> if ((stat_config.aggr_mode == AGGR_THREAD) && (target.system_wide))
> target.per_thread = true;
>
> + stat_config.system_wide = target.system_wide;
> + if (target.cpu_list)
> + stat_config.user_requested_cpu_list = strdup(target.cpu_list);

Check strdup() result?

> +
> /*
> * Metric parsing needs to be delayed as metrics may optimize events
> * knowing the target is system-wide.
> @@ -2443,6 +2449,8 @@ int cmd_stat(int argc, const char **argv)
> metricgroup__parse_groups(evsel_list, metrics,
> stat_config.metric_no_group,
> stat_config.metric_no_merge,
> + stat_config.user_requested_cpu_list,
> + stat_config.system_wide,
> &stat_config.metric_events);
> zfree(&metrics);
> }
> @@ -2633,6 +2641,7 @@ int cmd_stat(int argc, const char **argv)
> iostat_release(evsel_list);
>
> zfree(&stat_config.walltime_run);
> + zfree(&stat_config.user_requested_cpu_list);
>
> if (smi_cost && smi_reset)
> sysfs__write_int(FREEZE_ON_SMI_PATH, 0);
> diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
> index 8aa7dafa18b3..ce186bf663c4 100644
> --- a/tools/perf/util/expr.c
> +++ b/tools/perf/util/expr.c
> @@ -310,7 +310,9 @@ struct expr_parse_ctx *expr__ctx_new(void)
> free(ctx);
> return NULL;
> }
> + ctx->sctx.user_requested_cpu_list = NULL;
> ctx->sctx.runtime = 0;
> + ctx->sctx.system_wide = false;
>
> return ctx;
> }
> @@ -332,6 +334,7 @@ void expr__ctx_free(struct expr_parse_ctx *ctx)
> struct hashmap_entry *cur;
> size_t bkt;
>
> + free(ctx->sctx.user_requested_cpu_list);

Isn't better to use zfree(&) here?

> hashmap__for_each_entry(ctx->ids, cur, bkt) {
> free((char *)cur->key);
> free(cur->value);
> @@ -407,7 +410,7 @@ double arch_get_tsc_freq(void)
> }
> #endif
>
> -double expr__get_literal(const char *literal)
> +double expr__get_literal(const char *literal, const struct expr_scanner_ctx *ctx)
> {
> static struct cpu_topology *topology;
> double result = NAN;
> @@ -439,6 +442,11 @@ double expr__get_literal(const char *literal)
> result = smt_on(topology) ? 1.0 : 0.0;
> goto out;
> }
> + if (!strcmp("#core_wide", literal)) {
> + result = core_wide(ctx->system_wide, ctx->user_requested_cpu_list, topology)
> + ? 1.0 : 0.0;
> + goto out;
> + }
> if (!strcmp("#num_packages", literal)) {
> result = topology->package_cpus_lists;
> goto out;
> diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
> index de9b886ec49a..32740e4c81ef 100644
> --- a/tools/perf/util/expr.h
> +++ b/tools/perf/util/expr.h
> @@ -14,7 +14,9 @@
> struct metric_ref;
>
> struct expr_scanner_ctx {
> + char *user_requested_cpu_list;
> int runtime;
> + bool system_wide;
> };
>
> struct expr_parse_ctx {
> @@ -58,6 +60,6 @@ int expr__find_ids(const char *expr, const char *one,
>
> double expr_id_data__value(const struct expr_id_data *data);
> double expr_id_data__source_count(const struct expr_id_data *data);
> -double expr__get_literal(const char *literal);
> +double expr__get_literal(const char *literal, const struct expr_scanner_ctx *ctx);
>
> #endif
> diff --git a/tools/perf/util/expr.l b/tools/perf/util/expr.l
> index 4dc8edbfd9ce..0168a9637330 100644
> --- a/tools/perf/util/expr.l
> +++ b/tools/perf/util/expr.l
> @@ -79,11 +79,11 @@ static int str(yyscan_t scanner, int token, int runtime)
> return token;
> }
>
> -static int literal(yyscan_t scanner)
> +static int literal(yyscan_t scanner, const struct expr_scanner_ctx *sctx)
> {
> YYSTYPE *yylval = expr_get_lval(scanner);
>
> - yylval->num = expr__get_literal(expr_get_text(scanner));
> + yylval->num = expr__get_literal(expr_get_text(scanner), sctx);
> if (isnan(yylval->num))
> return EXPR_ERROR;
>
> @@ -108,7 +108,7 @@ min { return MIN; }
> if { return IF; }
> else { return ELSE; }
> source_count { return SOURCE_COUNT; }
> -{literal} { return literal(yyscanner); }
> +{literal} { return literal(yyscanner, sctx); }
> {number} { return value(yyscanner); }
> {symbol} { return str(yyscanner, ID, sctx->runtime); }
> "|" { return '|'; }
> diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
> index 9151346a16ab..f7d93dc02326 100644
> --- a/tools/perf/util/metricgroup.c
> +++ b/tools/perf/util/metricgroup.c
> @@ -22,6 +22,7 @@
> #include <linux/list_sort.h>
> #include <linux/string.h>
> #include <linux/zalloc.h>
> +#include <perf/cpumap.h>
> #include <subcmd/parse-options.h>
> #include <api/fs/fs.h>
> #include "util.h"
> @@ -192,7 +193,9 @@ static bool metricgroup__has_constraint(const struct pmu_event *pe)
> static struct metric *metric__new(const struct pmu_event *pe,
> const char *modifier,
> bool metric_no_group,
> - int runtime)
> + int runtime,
> + const char *user_requested_cpu_list,
> + bool system_wide)
> {
> struct metric *m;
>
> @@ -215,7 +218,11 @@ static struct metric *metric__new(const struct pmu_event *pe,
> }
> m->metric_expr = pe->metric_expr;
> m->metric_unit = pe->unit;
> + m->pctx->sctx.user_requested_cpu_list = NULL;
> + if (user_requested_cpu_list)
> + m->pctx->sctx.user_requested_cpu_list = strdup(user_requested_cpu_list);

Check?

> m->pctx->sctx.runtime = runtime;
> + m->pctx->sctx.system_wide = system_wide;
> m->has_constraint = metric_no_group || metricgroup__has_constraint(pe);
> m->metric_refs = NULL;
> m->evlist = NULL;
> @@ -874,6 +881,8 @@ struct metricgroup_add_iter_data {
> int *ret;
> bool *has_match;
> bool metric_no_group;
> + const char *user_requested_cpu_list;
> + bool system_wide;
> struct metric *root_metric;
> const struct visited_metric *visited;
> const struct pmu_events_table *table;
> @@ -887,6 +896,8 @@ static int add_metric(struct list_head *metric_list,
> const struct pmu_event *pe,
> const char *modifier,
> bool metric_no_group,
> + const char *user_requested_cpu_list,
> + bool system_wide,
> struct metric *root_metric,
> const struct visited_metric *visited,
> const struct pmu_events_table *table);
> @@ -899,6 +910,8 @@ static int add_metric(struct list_head *metric_list,
> * @metric_no_group: Should events written to events be grouped "{}" or
> * global. Grouping is the default but due to multiplexing the
> * user may override.
> + * @user_requested_cpu_list: Command line specified CPUs to record on.
> + * @system_wide: Are events for all processes recorded.
> * @root_metric: Metrics may reference other metrics to form a tree. In this
> * case the root_metric holds all the IDs and a list of referenced
> * metrics. When adding a root this argument is NULL.
> @@ -910,6 +923,8 @@ static int add_metric(struct list_head *metric_list,
> static int resolve_metric(struct list_head *metric_list,
> const char *modifier,
> bool metric_no_group,
> + const char *user_requested_cpu_list,
> + bool system_wide,
> struct metric *root_metric,
> const struct visited_metric *visited,
> const struct pmu_events_table *table)
> @@ -956,7 +971,8 @@ static int resolve_metric(struct list_head *metric_list,
> */
> for (i = 0; i < pending_cnt; i++) {
> ret = add_metric(metric_list, &pending[i].pe, modifier, metric_no_group,
> - root_metric, visited, table);
> + user_requested_cpu_list, system_wide, root_metric, visited,
> + table);
> if (ret)
> break;
> }
> @@ -974,6 +990,8 @@ static int resolve_metric(struct list_head *metric_list,
> * global. Grouping is the default but due to multiplexing the
> * user may override.
> * @runtime: A special argument for the parser only known at runtime.
> + * @user_requested_cpu_list: Command line specified CPUs to record on.
> + * @system_wide: Are events for all processes recorded.
> * @root_metric: Metrics may reference other metrics to form a tree. In this
> * case the root_metric holds all the IDs and a list of referenced
> * metrics. When adding a root this argument is NULL.
> @@ -987,6 +1005,8 @@ static int __add_metric(struct list_head *metric_list,
> const char *modifier,
> bool metric_no_group,
> int runtime,
> + const char *user_requested_cpu_list,
> + bool system_wide,
> struct metric *root_metric,
> const struct visited_metric *visited,
> const struct pmu_events_table *table)
> @@ -1011,7 +1031,8 @@ static int __add_metric(struct list_head *metric_list,
> * This metric is the root of a tree and may reference other
> * metrics that are added recursively.
> */
> - root_metric = metric__new(pe, modifier, metric_no_group, runtime);
> + root_metric = metric__new(pe, modifier, metric_no_group, runtime,
> + user_requested_cpu_list, system_wide);
> if (!root_metric)
> return -ENOMEM;
>
> @@ -1060,8 +1081,9 @@ static int __add_metric(struct list_head *metric_list,
> ret = -EINVAL;
> } else {
> /* Resolve referenced metrics. */
> - ret = resolve_metric(metric_list, modifier, metric_no_group, root_metric,
> - &visited_node, table);
> + ret = resolve_metric(metric_list, modifier, metric_no_group,
> + user_requested_cpu_list, system_wide,
> + root_metric, &visited_node, table);
> }
>
> if (ret) {
> @@ -1109,6 +1131,8 @@ static int add_metric(struct list_head *metric_list,
> const struct pmu_event *pe,
> const char *modifier,
> bool metric_no_group,
> + const char *user_requested_cpu_list,
> + bool system_wide,
> struct metric *root_metric,
> const struct visited_metric *visited,
> const struct pmu_events_table *table)
> @@ -1119,7 +1143,8 @@ static int add_metric(struct list_head *metric_list,
>
> if (!strstr(pe->metric_expr, "?")) {
> ret = __add_metric(metric_list, pe, modifier, metric_no_group, 0,
> - root_metric, visited, table);
> + user_requested_cpu_list, system_wide, root_metric,
> + visited, table);
> } else {
> int j, count;
>
> @@ -1132,7 +1157,8 @@ static int add_metric(struct list_head *metric_list,
>
> for (j = 0; j < count && !ret; j++)
> ret = __add_metric(metric_list, pe, modifier, metric_no_group, j,
> - root_metric, visited, table);
> + user_requested_cpu_list, system_wide,
> + root_metric, visited, table);
> }
>
> return ret;
> @@ -1149,6 +1175,7 @@ static int metricgroup__add_metric_sys_event_iter(const struct pmu_event *pe,
> return 0;
>
> ret = add_metric(d->metric_list, pe, d->modifier, d->metric_no_group,
> + d->user_requested_cpu_list, d->system_wide,
> d->root_metric, d->visited, d->table);
> if (ret)
> goto out;
> @@ -1191,7 +1218,9 @@ struct metricgroup__add_metric_data {
> struct list_head *list;
> const char *metric_name;
> const char *modifier;
> + const char *user_requested_cpu_list;
> bool metric_no_group;
> + bool system_wide;
> bool has_match;
> };
>
> @@ -1208,8 +1237,8 @@ static int metricgroup__add_metric_callback(const struct pmu_event *pe,
>
> data->has_match = true;
> ret = add_metric(data->list, pe, data->modifier, data->metric_no_group,
> - /*root_metric=*/NULL,
> - /*visited_metrics=*/NULL, table);
> + data->user_requested_cpu_list, data->system_wide,
> + /*root_metric=*/NULL, /*visited_metrics=*/NULL, table);
> }
> return ret;
> }
> @@ -1223,12 +1252,16 @@ static int metricgroup__add_metric_callback(const struct pmu_event *pe,
> * @metric_no_group: Should events written to events be grouped "{}" or
> * global. Grouping is the default but due to multiplexing the
> * user may override.
> + * @user_requested_cpu_list: Command line specified CPUs to record on.
> + * @system_wide: Are events for all processes recorded.
> * @metric_list: The list that the metric or metric group are added to.
> * @table: The table that is searched for metrics, most commonly the table for the
> * architecture perf is running upon.
> */
> static int metricgroup__add_metric(const char *metric_name, const char *modifier,
> bool metric_no_group,
> + const char *user_requested_cpu_list,
> + bool system_wide,
> struct list_head *metric_list,
> const struct pmu_events_table *table)
> {
> @@ -1242,6 +1275,8 @@ static int metricgroup__add_metric(const char *metric_name, const char *modifier
> .metric_name = metric_name,
> .modifier = modifier,
> .metric_no_group = metric_no_group,
> + .user_requested_cpu_list = user_requested_cpu_list,
> + .system_wide = system_wide,
> .has_match = false,
> };
> /*
> @@ -1263,6 +1298,8 @@ static int metricgroup__add_metric(const char *metric_name, const char *modifier
> .metric_name = metric_name,
> .modifier = modifier,
> .metric_no_group = metric_no_group,
> + .user_requested_cpu_list = user_requested_cpu_list,
> + .system_wide = system_wide,
> .has_match = &has_match,
> .ret = &ret,
> .table = table,
> @@ -1293,12 +1330,15 @@ static int metricgroup__add_metric(const char *metric_name, const char *modifier
> * @metric_no_group: Should events written to events be grouped "{}" or
> * global. Grouping is the default but due to multiplexing the
> * user may override.
> + * @user_requested_cpu_list: Command line specified CPUs to record on.
> + * @system_wide: Are events for all processes recorded.
> * @metric_list: The list that metrics are added to.
> * @table: The table that is searched for metrics, most commonly the table for the
> * architecture perf is running upon.
> */
> static int metricgroup__add_metric_list(const char *list, bool metric_no_group,
> - struct list_head *metric_list,
> + const char *user_requested_cpu_list,
> + bool system_wide, struct list_head *metric_list,
> const struct pmu_events_table *table)
> {
> char *list_itr, *list_copy, *metric_name, *modifier;
> @@ -1315,8 +1355,8 @@ static int metricgroup__add_metric_list(const char *list, bool metric_no_group,
> *modifier++ = '\0';
>
> ret = metricgroup__add_metric(metric_name, modifier,
> - metric_no_group, metric_list,
> - table);
> + metric_no_group, user_requested_cpu_list,
> + system_wide, metric_list, table);
> if (ret == -EINVAL)
> pr_err("Cannot find metric or group `%s'\n", metric_name);
>
> @@ -1505,6 +1545,8 @@ static int parse_ids(bool metric_no_merge, struct perf_pmu *fake_pmu,
> static int parse_groups(struct evlist *perf_evlist, const char *str,
> bool metric_no_group,
> bool metric_no_merge,
> + const char *user_requested_cpu_list,
> + bool system_wide,
> struct perf_pmu *fake_pmu,
> struct rblist *metric_events_list,
> const struct pmu_events_table *table)
> @@ -1518,7 +1560,8 @@ static int parse_groups(struct evlist *perf_evlist, const char *str,
> if (metric_events_list->nr_entries == 0)
> metricgroup__rblist_init(metric_events_list);
> ret = metricgroup__add_metric_list(str, metric_no_group,
> - &metric_list, table);
> + user_requested_cpu_list,
> + system_wide, &metric_list, table);
> if (ret)
> goto out;
>
> @@ -1650,6 +1693,8 @@ int metricgroup__parse_groups(struct evlist *perf_evlist,
> const char *str,
> bool metric_no_group,
> bool metric_no_merge,
> + const char *user_requested_cpu_list,
> + bool system_wide,
> struct rblist *metric_events)
> {
> const struct pmu_events_table *table = pmu_events_table__find();
> @@ -1657,8 +1702,9 @@ int metricgroup__parse_groups(struct evlist *perf_evlist,
> if (!table)
> return -EINVAL;
>
> - return parse_groups(perf_evlist, str, metric_no_group,
> - metric_no_merge, NULL, metric_events, table);
> + return parse_groups(perf_evlist, str, metric_no_group, metric_no_merge,
> + user_requested_cpu_list, system_wide,
> + /*fake_pmu=*/NULL, metric_events, table);
> }
>
> int metricgroup__parse_groups_test(struct evlist *evlist,
> @@ -1668,8 +1714,10 @@ int metricgroup__parse_groups_test(struct evlist *evlist,
> bool metric_no_merge,
> struct rblist *metric_events)
> {
> - return parse_groups(evlist, str, metric_no_group,
> - metric_no_merge, &perf_pmu__fake, metric_events, table);
> + return parse_groups(evlist, str, metric_no_group, metric_no_merge,
> + /*user_requested_cpu_list=*/NULL,
> + /*system_wide=*/false,
> + &perf_pmu__fake, metric_events, table);
> }
>
> static int metricgroup__has_metric_callback(const struct pmu_event *pe,
> diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h
> index af9ceadaec0f..732d3a0d3334 100644
> --- a/tools/perf/util/metricgroup.h
> +++ b/tools/perf/util/metricgroup.h
> @@ -68,6 +68,8 @@ int metricgroup__parse_groups(struct evlist *perf_evlist,
> const char *str,
> bool metric_no_group,
> bool metric_no_merge,
> + const char *user_requested_cpu_list,
> + bool system_wide,
> struct rblist *metric_events);
> int metricgroup__parse_groups_test(struct evlist *evlist,
> const struct pmu_events_table *table,
> diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c
> index 1439acd109db..1deb75741df4 100644
> --- a/tools/perf/util/stat-shadow.c
> +++ b/tools/perf/util/stat-shadow.c
> @@ -911,7 +911,10 @@ static void generic_metric(struct perf_stat_config *config,
> if (!pctx)
> return;
>
> + if (config->user_requested_cpu_list)
> + pctx->sctx.user_requested_cpu_list = strdup(config->user_requested_cpu_list);
> pctx->sctx.runtime = runtime;
> + pctx->sctx.system_wide = config->system_wide;
> i = prepare_metric(metric_events, metric_refs, pctx, cpu_map_idx, st);
> if (i < 0) {
> expr__ctx_free(pctx);
> @@ -1304,7 +1307,8 @@ void perf_stat__print_shadow_stats(struct perf_stat_config *config,
> core_bound * 100.);
> } else if (evsel->metric_expr) {
> generic_metric(config, evsel->metric_expr, evsel->metric_events, NULL,
> - evsel->name, evsel->metric_name, NULL, 1, cpu_map_idx, out, st);
> + evsel->name, evsel->metric_name, NULL, 1,
> + cpu_map_idx, out, st);
> } else if (runtime_stat_n(st, STAT_NSECS, cpu_map_idx, &rsd) != 0) {
> char unit = ' ';
> char unit_buf[10] = "/sec";
> @@ -1329,8 +1333,9 @@ void perf_stat__print_shadow_stats(struct perf_stat_config *config,
> if (num++ > 0)
> out->new_line(config, ctxp);
> generic_metric(config, mexp->metric_expr, mexp->metric_events,
> - mexp->metric_refs, evsel->name, mexp->metric_name,
> - mexp->metric_unit, mexp->runtime, cpu_map_idx, out, st);
> + mexp->metric_refs, evsel->name, mexp->metric_name,
> + mexp->metric_unit, mexp->runtime,
> + cpu_map_idx, out, st);
> }
> }
> if (num == 0)
> diff --git a/tools/perf/util/stat.h b/tools/perf/util/stat.h
> index 668250022f8c..72713b344b79 100644
> --- a/tools/perf/util/stat.h
> +++ b/tools/perf/util/stat.h
> @@ -141,6 +141,8 @@ struct perf_stat_config {
> bool stop_read_counter;
> bool quiet;
> bool iostat_run;
> + char *user_requested_cpu_list;
> + bool system_wide;
> FILE *output;
> unsigned int interval;
> unsigned int timeout;
> --
> 2.37.2.672.g94769d06f0-goog

--

- Arnaldo

2022-08-31 15:24:41

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH v1 6/8] perf stat: Delay metric parsing

Em Tue, Aug 30, 2022 at 09:48:44AM -0700, Ian Rogers escreveu:
> Having metric parsing as part of argument processing causes issues as
> flags like metric-no-group may be specified later. It also denies the
> opportunity to optimize the events on SMT systems where fewer events
> may be possible if we know the target is system-wide. Move metric
> parsing to after command line option parsing. Because of how stat runs
> this moves the parsing after record/report which fail to work with
> metrics currently anyway.
>
> Signed-off-by: Ian Rogers <[email protected]>
> ---
> tools/perf/builtin-stat.c | 48 ++++++++++++++++++++++++-----------
> tools/perf/util/metricgroup.c | 3 +--
> tools/perf/util/metricgroup.h | 2 +-
> 3 files changed, 35 insertions(+), 18 deletions(-)
>
> diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
> index 7fb81a44672d..c813b1aa7d7c 100644
> --- a/tools/perf/builtin-stat.c
> +++ b/tools/perf/builtin-stat.c
> @@ -191,6 +191,7 @@ static bool append_file;
> static bool interval_count;
> static const char *output_name;
> static int output_fd;
> +static char *metrics;
>
> struct perf_stat {
> bool record;
> @@ -1147,14 +1148,21 @@ static int enable_metric_only(const struct option *opt __maybe_unused,
> return 0;
> }
>
> -static int parse_metric_groups(const struct option *opt,
> +static int append_metric_groups(const struct option *opt __maybe_unused,
> const char *str,
> int unset __maybe_unused)
> {
> - return metricgroup__parse_groups(opt, str,
> - stat_config.metric_no_group,
> - stat_config.metric_no_merge,
> - &stat_config.metric_events);
> + if (metrics) {
> + char *tmp;
> +
> + if (asprintf(&tmp, "%s,%s", metrics, str) < 0)
> + return -ENOMEM;

We check if we managed to allocate memory here, but not later at
strdup()?

> + free(metrics);
> + metrics = tmp;
> + } else {
> + metrics = strdup(str);
> + }
> + return 0;
> }
>
> static int parse_control_option(const struct option *opt,
> @@ -1298,7 +1306,7 @@ static struct option stat_options[] = {
> "measure SMI cost"),
> OPT_CALLBACK('M', "metrics", &evsel_list, "metric/metric group list",
> "monitor specified metrics or metric groups (separated by ,)",
> - parse_metric_groups),
> + append_metric_groups),
> OPT_BOOLEAN_FLAG(0, "all-kernel", &stat_config.all_kernel,
> "Configure all used events to run in kernel space.",
> PARSE_OPT_EXCLUSIVE),
> @@ -1791,11 +1799,9 @@ static int add_default_attributes(void)
> * on an architecture test for such a metric name.
> */
> if (metricgroup__has_metric("transaction")) {
> - struct option opt = { .value = &evsel_list };
> -
> - return metricgroup__parse_groups(&opt, "transaction",
> + return metricgroup__parse_groups(evsel_list, "transaction",
> stat_config.metric_no_group,
> - stat_config.metric_no_merge,
> + stat_config.metric_no_merge,
> &stat_config.metric_events);
> }
>
> @@ -2260,8 +2266,6 @@ int cmd_stat(int argc, const char **argv)
> argc = parse_options_subcommand(argc, argv, stat_options, stat_subcommands,
> (const char **) stat_usage,
> PARSE_OPT_STOP_AT_NON_OPTION);
> - perf_stat__collect_metric_expr(evsel_list);
> - perf_stat__init_shadow_stats();
>
> if (stat_config.csv_sep) {
> stat_config.csv_output = true;
> @@ -2428,6 +2432,23 @@ int cmd_stat(int argc, const char **argv)
> target.system_wide = true;
> }
>
> + if ((stat_config.aggr_mode == AGGR_THREAD) && (target.system_wide))
> + target.per_thread = true;
> +
> + /*
> + * Metric parsing needs to be delayed as metrics may optimize events
> + * knowing the target is system-wide.
> + */
> + if (metrics) {
> + metricgroup__parse_groups(evsel_list, metrics,
> + stat_config.metric_no_group,
> + stat_config.metric_no_merge,
> + &stat_config.metric_events);
> + zfree(&metrics);
> + }
> + perf_stat__collect_metric_expr(evsel_list);
> + perf_stat__init_shadow_stats();
> +
> if (add_default_attributes())
> goto out;
>
> @@ -2447,9 +2468,6 @@ int cmd_stat(int argc, const char **argv)
> }
> }
>
> - if ((stat_config.aggr_mode == AGGR_THREAD) && (target.system_wide))
> - target.per_thread = true;
> -
> if (evlist__fix_hybrid_cpus(evsel_list, target.cpu_list)) {
> pr_err("failed to use cpu list %s\n", target.cpu_list);
> goto out;
> diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
> index b144c3e35264..9151346a16ab 100644
> --- a/tools/perf/util/metricgroup.c
> +++ b/tools/perf/util/metricgroup.c
> @@ -1646,13 +1646,12 @@ static int parse_groups(struct evlist *perf_evlist, const char *str,
> return ret;
> }
>
> -int metricgroup__parse_groups(const struct option *opt,
> +int metricgroup__parse_groups(struct evlist *perf_evlist,
> const char *str,
> bool metric_no_group,
> bool metric_no_merge,
> struct rblist *metric_events)
> {
> - struct evlist *perf_evlist = *(struct evlist **)opt->value;
> const struct pmu_events_table *table = pmu_events_table__find();
>
> if (!table)
> diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h
> index 016b3b1a289a..af9ceadaec0f 100644
> --- a/tools/perf/util/metricgroup.h
> +++ b/tools/perf/util/metricgroup.h
> @@ -64,7 +64,7 @@ struct metric_expr {
> struct metric_event *metricgroup__lookup(struct rblist *metric_events,
> struct evsel *evsel,
> bool create);
> -int metricgroup__parse_groups(const struct option *opt,
> +int metricgroup__parse_groups(struct evlist *perf_evlist,
> const char *str,
> bool metric_no_group,
> bool metric_no_merge,
> --
> 2.37.2.672.g94769d06f0-goog

--

- Arnaldo

2022-08-31 15:42:00

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH v1 1/8] perf smt: Tidy header guard add SPDX

Em Tue, Aug 30, 2022 at 09:48:39AM -0700, Ian Rogers escreveu:
> Make the header guard consistent with others.

Thanks, applied to perf/core, pushing it out now.

- Arnaldo


> Signed-off-by: Ian Rogers <[email protected]>
> ---
> tools/perf/util/smt.c | 1 +
> tools/perf/util/smt.h | 7 ++++---
> 2 files changed, 5 insertions(+), 3 deletions(-)
>
> diff --git a/tools/perf/util/smt.c b/tools/perf/util/smt.c
> index 2b0a36ebf27a..8fed03283c85 100644
> --- a/tools/perf/util/smt.c
> +++ b/tools/perf/util/smt.c
> @@ -1,3 +1,4 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> #include <stdio.h>
> #include <stdlib.h>
> #include <unistd.h>
> diff --git a/tools/perf/util/smt.h b/tools/perf/util/smt.h
> index b8414b7bcbc8..a98d65808f6a 100644
> --- a/tools/perf/util/smt.h
> +++ b/tools/perf/util/smt.h
> @@ -1,6 +1,7 @@
> -#ifndef SMT_H
> -#define SMT_H 1
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#ifndef __SMT_H
> +#define __SMT_H 1
>
> int smt_on(void);
>
> -#endif
> +#endif /* __SMT_H */
> --
> 2.37.2.672.g94769d06f0-goog

--

- Arnaldo

2022-08-31 16:08:21

by Ian Rogers

[permalink] [raw]
Subject: Re: [PATCH v1 5/8] perf topology: Add core_wide

On Wed, Aug 31, 2022 at 7:40 AM Arnaldo Carvalho de Melo
<[email protected]> wrote:
>
> Em Tue, Aug 30, 2022 at 09:48:43AM -0700, Ian Rogers escreveu:
> > It is possible to optimize metrics when all SMT threads (CPUs) on a
> > core are measuring events in system wide mode. For example, TMA
> > metrics defines CORE_CLKS for Sandybrdige as:
> >
> > if SMT is disabled:
> > CPU_CLK_UNHALTED.THREAD
> > if SMT is enabled and recording on all SMT threads:
> > CPU_CLK_UNHALTED.THREAD_ANY / 2
> > if SMT is enabled and not recording on all SMT threads:
> > (CPU_CLK_UNHALTED.THREAD/2)*
> > (1+CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE/CPU_CLK_UNHALTED.REF_XCLK )
> >
> > That is two more events are necessary when not gathering counts on all
> > SMT threads. To distinguish all SMT threads on a core vs system wide
> > (all CPUs) call the new property core wide. Add a core wide test that
> > determines the property from user requested CPUs, the topology and
> > system wide. System wide is required as other processes running on a
> > SMT thread will change the counts.
> >
> > Signed-off-by: Ian Rogers <[email protected]>
> > ---
> > tools/perf/util/cputopo.c | 46 +++++++++++++++++++++++++++++++++++++++
> > tools/perf/util/cputopo.h | 3 +++
> > tools/perf/util/smt.c | 14 ++++++++++++
> > tools/perf/util/smt.h | 7 ++++++
> > 4 files changed, 70 insertions(+)
> >
> > diff --git a/tools/perf/util/cputopo.c b/tools/perf/util/cputopo.c
> > index 511002e52714..1a3ff6449158 100644
> > --- a/tools/perf/util/cputopo.c
> > +++ b/tools/perf/util/cputopo.c
> > @@ -172,6 +172,52 @@ bool cpu_topology__smt_on(const struct cpu_topology *topology)
> > return false;
> > }
> >
> > +bool cpu_topology__core_wide(const struct cpu_topology *topology,
> > + const char *user_requested_cpu_list)
> > +{
> > + struct perf_cpu_map *user_requested_cpus;
> > +
> > + /*
> > + * If user_requested_cpu_list is empty then all CPUs are recorded and so
> > + * core_wide is true.
> > + */
> > + if (!user_requested_cpu_list)
> > + return true;
> > +
> > + user_requested_cpus = perf_cpu_map__new(user_requested_cpu_list);
>
> Don't we need a NULL test here?

No, NULL is a valid perf_cpu_map, one with no values in it. For
example, see here:
https://git.kernel.org/pub/scm/linux/kernel/git/acme/linux.git/tree/tools/lib/perf/cpumap.c?h=perf/core#n266
So there's no way to tell an error (ENOMEM) from an empty map. My
suggestion is a rewrite of perf_cpu_map, I'd like to do this in a new
"libperf2" which has a more permissive than GPL license like libbpf.
That is out-of-scope here.

Thanks,
Ian

> > + /* Check that every user requested CPU is the complete set of SMT threads on a core. */
> > + for (u32 i = 0; i < topology->core_cpus_lists; i++) {
> > + const char *core_cpu_list = topology->core_cpus_list[i];
> > + struct perf_cpu_map *core_cpus = perf_cpu_map__new(core_cpu_list);
>
> Here too, no?
>
> > + struct perf_cpu cpu;
> > + int idx;
> > + bool has_first, first = true;
> > +
> > + perf_cpu_map__for_each_cpu(cpu, idx, core_cpus) {
> > + if (first) {
> > + has_first = perf_cpu_map__has(user_requested_cpus, cpu);
> > + first = false;
> > + } else {
> > + /*
> > + * If the first core CPU is user requested then
> > + * all subsequent CPUs in the core must be user
> > + * requested too. If the first CPU isn't user
> > + * requested then none of the others must be
> > + * too.
> > + */
> > + if (perf_cpu_map__has(user_requested_cpus, cpu) != has_first) {
> > + perf_cpu_map__put(core_cpus);
> > + perf_cpu_map__put(user_requested_cpus);
> > + return false;
> > + }
> > + }
> > + }
> > + perf_cpu_map__put(core_cpus);
> > + }
> > + perf_cpu_map__put(user_requested_cpus);
> > + return true;
> > +}
> > +
> > static bool has_die_topology(void)
> > {
> > char filename[MAXPATHLEN];
> > diff --git a/tools/perf/util/cputopo.h b/tools/perf/util/cputopo.h
> > index 469db775a13c..969e5920a00e 100644
> > --- a/tools/perf/util/cputopo.h
> > +++ b/tools/perf/util/cputopo.h
> > @@ -60,6 +60,9 @@ struct cpu_topology *cpu_topology__new(void);
> > void cpu_topology__delete(struct cpu_topology *tp);
> > /* Determine from the core list whether SMT was enabled. */
> > bool cpu_topology__smt_on(const struct cpu_topology *topology);
> > +/* Are the sets of SMT siblings all enabled or all disabled in user_requested_cpus. */
> > +bool cpu_topology__core_wide(const struct cpu_topology *topology,
> > + const char *user_requested_cpu_list);
> >
> > struct numa_topology *numa_topology__new(void);
> > void numa_topology__delete(struct numa_topology *tp);
> > diff --git a/tools/perf/util/smt.c b/tools/perf/util/smt.c
> > index ce90c4ee4138..994e9e418227 100644
> > --- a/tools/perf/util/smt.c
> > +++ b/tools/perf/util/smt.c
> > @@ -21,3 +21,17 @@ bool smt_on(const struct cpu_topology *topology)
> > cached = true;
> > return cached_result;
> > }
> > +
> > +bool core_wide(bool system_wide, const char *user_requested_cpu_list,
> > + const struct cpu_topology *topology)
> > +{
> > + /* If not everything running on a core is being recorded then we can't use core_wide. */
> > + if (!system_wide)
> > + return false;
> > +
> > + /* Cheap case that SMT is disabled and therefore we're inherently core_wide. */
> > + if (!smt_on(topology))
> > + return true;
> > +
> > + return cpu_topology__core_wide(topology, user_requested_cpu_list);
> > +}
> > diff --git a/tools/perf/util/smt.h b/tools/perf/util/smt.h
> > index e26999c6b8d4..ae9095f2c38c 100644
> > --- a/tools/perf/util/smt.h
> > +++ b/tools/perf/util/smt.h
> > @@ -7,4 +7,11 @@ struct cpu_topology;
> > /* Returns true if SMT (aka hyperthreading) is enabled. */
> > bool smt_on(const struct cpu_topology *topology);
> >
> > +/*
> > + * Returns true when system wide and all SMT threads for a core are in the
> > + * user_requested_cpus map.
> > + */
> > +bool core_wide(bool system_wide, const char *user_requested_cpu_list,
> > + const struct cpu_topology *topology);
> > +
> > #endif /* __SMT_H */
> > --
> > 2.37.2.672.g94769d06f0-goog
>
> --
>
> - Arnaldo

2022-08-31 16:23:04

by Ian Rogers

[permalink] [raw]
Subject: Re: [PATCH v1 6/8] perf stat: Delay metric parsing

On Wed, Aug 31, 2022 at 7:42 AM Arnaldo Carvalho de Melo
<[email protected]> wrote:
>
> Em Tue, Aug 30, 2022 at 09:48:44AM -0700, Ian Rogers escreveu:
> > Having metric parsing as part of argument processing causes issues as
> > flags like metric-no-group may be specified later. It also denies the
> > opportunity to optimize the events on SMT systems where fewer events
> > may be possible if we know the target is system-wide. Move metric
> > parsing to after command line option parsing. Because of how stat runs
> > this moves the parsing after record/report which fail to work with
> > metrics currently anyway.
> >
> > Signed-off-by: Ian Rogers <[email protected]>
> > ---
> > tools/perf/builtin-stat.c | 48 ++++++++++++++++++++++++-----------
> > tools/perf/util/metricgroup.c | 3 +--
> > tools/perf/util/metricgroup.h | 2 +-
> > 3 files changed, 35 insertions(+), 18 deletions(-)
> >
> > diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
> > index 7fb81a44672d..c813b1aa7d7c 100644
> > --- a/tools/perf/builtin-stat.c
> > +++ b/tools/perf/builtin-stat.c
> > @@ -191,6 +191,7 @@ static bool append_file;
> > static bool interval_count;
> > static const char *output_name;
> > static int output_fd;
> > +static char *metrics;
> >
> > struct perf_stat {
> > bool record;
> > @@ -1147,14 +1148,21 @@ static int enable_metric_only(const struct option *opt __maybe_unused,
> > return 0;
> > }
> >
> > -static int parse_metric_groups(const struct option *opt,
> > +static int append_metric_groups(const struct option *opt __maybe_unused,
> > const char *str,
> > int unset __maybe_unused)
> > {
> > - return metricgroup__parse_groups(opt, str,
> > - stat_config.metric_no_group,
> > - stat_config.metric_no_merge,
> > - &stat_config.metric_events);
> > + if (metrics) {
> > + char *tmp;
> > +
> > + if (asprintf(&tmp, "%s,%s", metrics, str) < 0)
> > + return -ENOMEM;
>
> We check if we managed to allocate memory here, but not later at
> strdup()?

Added to v2.

Thanks,
Ian

> > + free(metrics);
> > + metrics = tmp;
> > + } else {
> > + metrics = strdup(str);
> > + }
> > + return 0;
> > }
> >
> > static int parse_control_option(const struct option *opt,
> > @@ -1298,7 +1306,7 @@ static struct option stat_options[] = {
> > "measure SMI cost"),
> > OPT_CALLBACK('M', "metrics", &evsel_list, "metric/metric group list",
> > "monitor specified metrics or metric groups (separated by ,)",
> > - parse_metric_groups),
> > + append_metric_groups),
> > OPT_BOOLEAN_FLAG(0, "all-kernel", &stat_config.all_kernel,
> > "Configure all used events to run in kernel space.",
> > PARSE_OPT_EXCLUSIVE),
> > @@ -1791,11 +1799,9 @@ static int add_default_attributes(void)
> > * on an architecture test for such a metric name.
> > */
> > if (metricgroup__has_metric("transaction")) {
> > - struct option opt = { .value = &evsel_list };
> > -
> > - return metricgroup__parse_groups(&opt, "transaction",
> > + return metricgroup__parse_groups(evsel_list, "transaction",
> > stat_config.metric_no_group,
> > - stat_config.metric_no_merge,
> > + stat_config.metric_no_merge,
> > &stat_config.metric_events);
> > }
> >
> > @@ -2260,8 +2266,6 @@ int cmd_stat(int argc, const char **argv)
> > argc = parse_options_subcommand(argc, argv, stat_options, stat_subcommands,
> > (const char **) stat_usage,
> > PARSE_OPT_STOP_AT_NON_OPTION);
> > - perf_stat__collect_metric_expr(evsel_list);
> > - perf_stat__init_shadow_stats();
> >
> > if (stat_config.csv_sep) {
> > stat_config.csv_output = true;
> > @@ -2428,6 +2432,23 @@ int cmd_stat(int argc, const char **argv)
> > target.system_wide = true;
> > }
> >
> > + if ((stat_config.aggr_mode == AGGR_THREAD) && (target.system_wide))
> > + target.per_thread = true;
> > +
> > + /*
> > + * Metric parsing needs to be delayed as metrics may optimize events
> > + * knowing the target is system-wide.
> > + */
> > + if (metrics) {
> > + metricgroup__parse_groups(evsel_list, metrics,
> > + stat_config.metric_no_group,
> > + stat_config.metric_no_merge,
> > + &stat_config.metric_events);
> > + zfree(&metrics);
> > + }
> > + perf_stat__collect_metric_expr(evsel_list);
> > + perf_stat__init_shadow_stats();
> > +
> > if (add_default_attributes())
> > goto out;
> >
> > @@ -2447,9 +2468,6 @@ int cmd_stat(int argc, const char **argv)
> > }
> > }
> >
> > - if ((stat_config.aggr_mode == AGGR_THREAD) && (target.system_wide))
> > - target.per_thread = true;
> > -
> > if (evlist__fix_hybrid_cpus(evsel_list, target.cpu_list)) {
> > pr_err("failed to use cpu list %s\n", target.cpu_list);
> > goto out;
> > diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
> > index b144c3e35264..9151346a16ab 100644
> > --- a/tools/perf/util/metricgroup.c
> > +++ b/tools/perf/util/metricgroup.c
> > @@ -1646,13 +1646,12 @@ static int parse_groups(struct evlist *perf_evlist, const char *str,
> > return ret;
> > }
> >
> > -int metricgroup__parse_groups(const struct option *opt,
> > +int metricgroup__parse_groups(struct evlist *perf_evlist,
> > const char *str,
> > bool metric_no_group,
> > bool metric_no_merge,
> > struct rblist *metric_events)
> > {
> > - struct evlist *perf_evlist = *(struct evlist **)opt->value;
> > const struct pmu_events_table *table = pmu_events_table__find();
> >
> > if (!table)
> > diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h
> > index 016b3b1a289a..af9ceadaec0f 100644
> > --- a/tools/perf/util/metricgroup.h
> > +++ b/tools/perf/util/metricgroup.h
> > @@ -64,7 +64,7 @@ struct metric_expr {
> > struct metric_event *metricgroup__lookup(struct rblist *metric_events,
> > struct evsel *evsel,
> > bool create);
> > -int metricgroup__parse_groups(const struct option *opt,
> > +int metricgroup__parse_groups(struct evlist *perf_evlist,
> > const char *str,
> > bool metric_no_group,
> > bool metric_no_merge,
> > --
> > 2.37.2.672.g94769d06f0-goog
>
> --
>
> - Arnaldo

2022-08-31 16:58:59

by Ian Rogers

[permalink] [raw]
Subject: Re: [PATCH v1 7/8] perf metrics: Wire up core_wide

On Wed, Aug 31, 2022 at 7:44 AM Arnaldo Carvalho de Melo
<[email protected]> wrote:
>
> Em Tue, Aug 30, 2022 at 09:48:45AM -0700, Ian Rogers escreveu:
> > Pass state necessary for core_wide into the expression parser. Add
> > system_wide and user_requested_cpu_list to perf_stat_config to make it
> > available at display time. evlist isn't used as the
> > evlist__create_maps, that computes user_requested_cpus, needs the list
> > of events which is generated by the metric.
> >
> > Signed-off-by: Ian Rogers <[email protected]>
> > ---
> > tools/perf/builtin-stat.c | 9 ++++
> > tools/perf/util/expr.c | 10 ++++-
> > tools/perf/util/expr.h | 4 +-
> > tools/perf/util/expr.l | 6 +--
> > tools/perf/util/metricgroup.c | 82 +++++++++++++++++++++++++++--------
> > tools/perf/util/metricgroup.h | 2 +
> > tools/perf/util/stat-shadow.c | 11 +++--
> > tools/perf/util/stat.h | 2 +
> > 8 files changed, 101 insertions(+), 25 deletions(-)
> >
> > diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
> > index c813b1aa7d7c..0554ba6547a5 100644
> > --- a/tools/perf/builtin-stat.c
> > +++ b/tools/perf/builtin-stat.c
> > @@ -1802,6 +1802,8 @@ static int add_default_attributes(void)
> > return metricgroup__parse_groups(evsel_list, "transaction",
> > stat_config.metric_no_group,
> > stat_config.metric_no_merge,
> > + stat_config.user_requested_cpu_list,
> > + stat_config.system_wide,
> > &stat_config.metric_events);
> > }
> >
> > @@ -2435,6 +2437,10 @@ int cmd_stat(int argc, const char **argv)
> > if ((stat_config.aggr_mode == AGGR_THREAD) && (target.system_wide))
> > target.per_thread = true;
> >
> > + stat_config.system_wide = target.system_wide;
> > + if (target.cpu_list)
> > + stat_config.user_requested_cpu_list = strdup(target.cpu_list);
>
> Check strdup() result?

Added to v2.

> > +
> > /*
> > * Metric parsing needs to be delayed as metrics may optimize events
> > * knowing the target is system-wide.
> > @@ -2443,6 +2449,8 @@ int cmd_stat(int argc, const char **argv)
> > metricgroup__parse_groups(evsel_list, metrics,
> > stat_config.metric_no_group,
> > stat_config.metric_no_merge,
> > + stat_config.user_requested_cpu_list,
> > + stat_config.system_wide,
> > &stat_config.metric_events);
> > zfree(&metrics);
> > }
> > @@ -2633,6 +2641,7 @@ int cmd_stat(int argc, const char **argv)
> > iostat_release(evsel_list);
> >
> > zfree(&stat_config.walltime_run);
> > + zfree(&stat_config.user_requested_cpu_list);
> >
> > if (smi_cost && smi_reset)
> > sysfs__write_int(FREEZE_ON_SMI_PATH, 0);
> > diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c
> > index 8aa7dafa18b3..ce186bf663c4 100644
> > --- a/tools/perf/util/expr.c
> > +++ b/tools/perf/util/expr.c
> > @@ -310,7 +310,9 @@ struct expr_parse_ctx *expr__ctx_new(void)
> > free(ctx);
> > return NULL;
> > }
> > + ctx->sctx.user_requested_cpu_list = NULL;
> > ctx->sctx.runtime = 0;
> > + ctx->sctx.system_wide = false;
> >
> > return ctx;
> > }
> > @@ -332,6 +334,7 @@ void expr__ctx_free(struct expr_parse_ctx *ctx)
> > struct hashmap_entry *cur;
> > size_t bkt;
> >
> > + free(ctx->sctx.user_requested_cpu_list);
>
> Isn't better to use zfree(&) here?

ctx is freed just below.

> > hashmap__for_each_entry(ctx->ids, cur, bkt) {
> > free((char *)cur->key);
> > free(cur->value);
> > @@ -407,7 +410,7 @@ double arch_get_tsc_freq(void)
> > }
> > #endif
> >
> > -double expr__get_literal(const char *literal)
> > +double expr__get_literal(const char *literal, const struct expr_scanner_ctx *ctx)
> > {
> > static struct cpu_topology *topology;
> > double result = NAN;
> > @@ -439,6 +442,11 @@ double expr__get_literal(const char *literal)
> > result = smt_on(topology) ? 1.0 : 0.0;
> > goto out;
> > }
> > + if (!strcmp("#core_wide", literal)) {
> > + result = core_wide(ctx->system_wide, ctx->user_requested_cpu_list, topology)
> > + ? 1.0 : 0.0;
> > + goto out;
> > + }
> > if (!strcmp("#num_packages", literal)) {
> > result = topology->package_cpus_lists;
> > goto out;
> > diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h
> > index de9b886ec49a..32740e4c81ef 100644
> > --- a/tools/perf/util/expr.h
> > +++ b/tools/perf/util/expr.h
> > @@ -14,7 +14,9 @@
> > struct metric_ref;
> >
> > struct expr_scanner_ctx {
> > + char *user_requested_cpu_list;
> > int runtime;
> > + bool system_wide;
> > };
> >
> > struct expr_parse_ctx {
> > @@ -58,6 +60,6 @@ int expr__find_ids(const char *expr, const char *one,
> >
> > double expr_id_data__value(const struct expr_id_data *data);
> > double expr_id_data__source_count(const struct expr_id_data *data);
> > -double expr__get_literal(const char *literal);
> > +double expr__get_literal(const char *literal, const struct expr_scanner_ctx *ctx);
> >
> > #endif
> > diff --git a/tools/perf/util/expr.l b/tools/perf/util/expr.l
> > index 4dc8edbfd9ce..0168a9637330 100644
> > --- a/tools/perf/util/expr.l
> > +++ b/tools/perf/util/expr.l
> > @@ -79,11 +79,11 @@ static int str(yyscan_t scanner, int token, int runtime)
> > return token;
> > }
> >
> > -static int literal(yyscan_t scanner)
> > +static int literal(yyscan_t scanner, const struct expr_scanner_ctx *sctx)
> > {
> > YYSTYPE *yylval = expr_get_lval(scanner);
> >
> > - yylval->num = expr__get_literal(expr_get_text(scanner));
> > + yylval->num = expr__get_literal(expr_get_text(scanner), sctx);
> > if (isnan(yylval->num))
> > return EXPR_ERROR;
> >
> > @@ -108,7 +108,7 @@ min { return MIN; }
> > if { return IF; }
> > else { return ELSE; }
> > source_count { return SOURCE_COUNT; }
> > -{literal} { return literal(yyscanner); }
> > +{literal} { return literal(yyscanner, sctx); }
> > {number} { return value(yyscanner); }
> > {symbol} { return str(yyscanner, ID, sctx->runtime); }
> > "|" { return '|'; }
> > diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c
> > index 9151346a16ab..f7d93dc02326 100644
> > --- a/tools/perf/util/metricgroup.c
> > +++ b/tools/perf/util/metricgroup.c
> > @@ -22,6 +22,7 @@
> > #include <linux/list_sort.h>
> > #include <linux/string.h>
> > #include <linux/zalloc.h>
> > +#include <perf/cpumap.h>
> > #include <subcmd/parse-options.h>
> > #include <api/fs/fs.h>
> > #include "util.h"
> > @@ -192,7 +193,9 @@ static bool metricgroup__has_constraint(const struct pmu_event *pe)
> > static struct metric *metric__new(const struct pmu_event *pe,
> > const char *modifier,
> > bool metric_no_group,
> > - int runtime)
> > + int runtime,
> > + const char *user_requested_cpu_list,
> > + bool system_wide)
> > {
> > struct metric *m;
> >
> > @@ -215,7 +218,11 @@ static struct metric *metric__new(const struct pmu_event *pe,
> > }
> > m->metric_expr = pe->metric_expr;
> > m->metric_unit = pe->unit;
> > + m->pctx->sctx.user_requested_cpu_list = NULL;
> > + if (user_requested_cpu_list)
> > + m->pctx->sctx.user_requested_cpu_list = strdup(user_requested_cpu_list);
>
> Check?

Added to v2.

Thanks,
Ian

> > m->pctx->sctx.runtime = runtime;
> > + m->pctx->sctx.system_wide = system_wide;
> > m->has_constraint = metric_no_group || metricgroup__has_constraint(pe);
> > m->metric_refs = NULL;
> > m->evlist = NULL;
> > @@ -874,6 +881,8 @@ struct metricgroup_add_iter_data {
> > int *ret;
> > bool *has_match;
> > bool metric_no_group;
> > + const char *user_requested_cpu_list;
> > + bool system_wide;
> > struct metric *root_metric;
> > const struct visited_metric *visited;
> > const struct pmu_events_table *table;
> > @@ -887,6 +896,8 @@ static int add_metric(struct list_head *metric_list,
> > const struct pmu_event *pe,
> > const char *modifier,
> > bool metric_no_group,
> > + const char *user_requested_cpu_list,
> > + bool system_wide,
> > struct metric *root_metric,
> > const struct visited_metric *visited,
> > const struct pmu_events_table *table);
> > @@ -899,6 +910,8 @@ static int add_metric(struct list_head *metric_list,
> > * @metric_no_group: Should events written to events be grouped "{}" or
> > * global. Grouping is the default but due to multiplexing the
> > * user may override.
> > + * @user_requested_cpu_list: Command line specified CPUs to record on.
> > + * @system_wide: Are events for all processes recorded.
> > * @root_metric: Metrics may reference other metrics to form a tree. In this
> > * case the root_metric holds all the IDs and a list of referenced
> > * metrics. When adding a root this argument is NULL.
> > @@ -910,6 +923,8 @@ static int add_metric(struct list_head *metric_list,
> > static int resolve_metric(struct list_head *metric_list,
> > const char *modifier,
> > bool metric_no_group,
> > + const char *user_requested_cpu_list,
> > + bool system_wide,
> > struct metric *root_metric,
> > const struct visited_metric *visited,
> > const struct pmu_events_table *table)
> > @@ -956,7 +971,8 @@ static int resolve_metric(struct list_head *metric_list,
> > */
> > for (i = 0; i < pending_cnt; i++) {
> > ret = add_metric(metric_list, &pending[i].pe, modifier, metric_no_group,
> > - root_metric, visited, table);
> > + user_requested_cpu_list, system_wide, root_metric, visited,
> > + table);
> > if (ret)
> > break;
> > }
> > @@ -974,6 +990,8 @@ static int resolve_metric(struct list_head *metric_list,
> > * global. Grouping is the default but due to multiplexing the
> > * user may override.
> > * @runtime: A special argument for the parser only known at runtime.
> > + * @user_requested_cpu_list: Command line specified CPUs to record on.
> > + * @system_wide: Are events for all processes recorded.
> > * @root_metric: Metrics may reference other metrics to form a tree. In this
> > * case the root_metric holds all the IDs and a list of referenced
> > * metrics. When adding a root this argument is NULL.
> > @@ -987,6 +1005,8 @@ static int __add_metric(struct list_head *metric_list,
> > const char *modifier,
> > bool metric_no_group,
> > int runtime,
> > + const char *user_requested_cpu_list,
> > + bool system_wide,
> > struct metric *root_metric,
> > const struct visited_metric *visited,
> > const struct pmu_events_table *table)
> > @@ -1011,7 +1031,8 @@ static int __add_metric(struct list_head *metric_list,
> > * This metric is the root of a tree and may reference other
> > * metrics that are added recursively.
> > */
> > - root_metric = metric__new(pe, modifier, metric_no_group, runtime);
> > + root_metric = metric__new(pe, modifier, metric_no_group, runtime,
> > + user_requested_cpu_list, system_wide);
> > if (!root_metric)
> > return -ENOMEM;
> >
> > @@ -1060,8 +1081,9 @@ static int __add_metric(struct list_head *metric_list,
> > ret = -EINVAL;
> > } else {
> > /* Resolve referenced metrics. */
> > - ret = resolve_metric(metric_list, modifier, metric_no_group, root_metric,
> > - &visited_node, table);
> > + ret = resolve_metric(metric_list, modifier, metric_no_group,
> > + user_requested_cpu_list, system_wide,
> > + root_metric, &visited_node, table);
> > }
> >
> > if (ret) {
> > @@ -1109,6 +1131,8 @@ static int add_metric(struct list_head *metric_list,
> > const struct pmu_event *pe,
> > const char *modifier,
> > bool metric_no_group,
> > + const char *user_requested_cpu_list,
> > + bool system_wide,
> > struct metric *root_metric,
> > const struct visited_metric *visited,
> > const struct pmu_events_table *table)
> > @@ -1119,7 +1143,8 @@ static int add_metric(struct list_head *metric_list,
> >
> > if (!strstr(pe->metric_expr, "?")) {
> > ret = __add_metric(metric_list, pe, modifier, metric_no_group, 0,
> > - root_metric, visited, table);
> > + user_requested_cpu_list, system_wide, root_metric,
> > + visited, table);
> > } else {
> > int j, count;
> >
> > @@ -1132,7 +1157,8 @@ static int add_metric(struct list_head *metric_list,
> >
> > for (j = 0; j < count && !ret; j++)
> > ret = __add_metric(metric_list, pe, modifier, metric_no_group, j,
> > - root_metric, visited, table);
> > + user_requested_cpu_list, system_wide,
> > + root_metric, visited, table);
> > }
> >
> > return ret;
> > @@ -1149,6 +1175,7 @@ static int metricgroup__add_metric_sys_event_iter(const struct pmu_event *pe,
> > return 0;
> >
> > ret = add_metric(d->metric_list, pe, d->modifier, d->metric_no_group,
> > + d->user_requested_cpu_list, d->system_wide,
> > d->root_metric, d->visited, d->table);
> > if (ret)
> > goto out;
> > @@ -1191,7 +1218,9 @@ struct metricgroup__add_metric_data {
> > struct list_head *list;
> > const char *metric_name;
> > const char *modifier;
> > + const char *user_requested_cpu_list;
> > bool metric_no_group;
> > + bool system_wide;
> > bool has_match;
> > };
> >
> > @@ -1208,8 +1237,8 @@ static int metricgroup__add_metric_callback(const struct pmu_event *pe,
> >
> > data->has_match = true;
> > ret = add_metric(data->list, pe, data->modifier, data->metric_no_group,
> > - /*root_metric=*/NULL,
> > - /*visited_metrics=*/NULL, table);
> > + data->user_requested_cpu_list, data->system_wide,
> > + /*root_metric=*/NULL, /*visited_metrics=*/NULL, table);
> > }
> > return ret;
> > }
> > @@ -1223,12 +1252,16 @@ static int metricgroup__add_metric_callback(const struct pmu_event *pe,
> > * @metric_no_group: Should events written to events be grouped "{}" or
> > * global. Grouping is the default but due to multiplexing the
> > * user may override.
> > + * @user_requested_cpu_list: Command line specified CPUs to record on.
> > + * @system_wide: Are events for all processes recorded.
> > * @metric_list: The list that the metric or metric group are added to.
> > * @table: The table that is searched for metrics, most commonly the table for the
> > * architecture perf is running upon.
> > */
> > static int metricgroup__add_metric(const char *metric_name, const char *modifier,
> > bool metric_no_group,
> > + const char *user_requested_cpu_list,
> > + bool system_wide,
> > struct list_head *metric_list,
> > const struct pmu_events_table *table)
> > {
> > @@ -1242,6 +1275,8 @@ static int metricgroup__add_metric(const char *metric_name, const char *modifier
> > .metric_name = metric_name,
> > .modifier = modifier,
> > .metric_no_group = metric_no_group,
> > + .user_requested_cpu_list = user_requested_cpu_list,
> > + .system_wide = system_wide,
> > .has_match = false,
> > };
> > /*
> > @@ -1263,6 +1298,8 @@ static int metricgroup__add_metric(const char *metric_name, const char *modifier
> > .metric_name = metric_name,
> > .modifier = modifier,
> > .metric_no_group = metric_no_group,
> > + .user_requested_cpu_list = user_requested_cpu_list,
> > + .system_wide = system_wide,
> > .has_match = &has_match,
> > .ret = &ret,
> > .table = table,
> > @@ -1293,12 +1330,15 @@ static int metricgroup__add_metric(const char *metric_name, const char *modifier
> > * @metric_no_group: Should events written to events be grouped "{}" or
> > * global. Grouping is the default but due to multiplexing the
> > * user may override.
> > + * @user_requested_cpu_list: Command line specified CPUs to record on.
> > + * @system_wide: Are events for all processes recorded.
> > * @metric_list: The list that metrics are added to.
> > * @table: The table that is searched for metrics, most commonly the table for the
> > * architecture perf is running upon.
> > */
> > static int metricgroup__add_metric_list(const char *list, bool metric_no_group,
> > - struct list_head *metric_list,
> > + const char *user_requested_cpu_list,
> > + bool system_wide, struct list_head *metric_list,
> > const struct pmu_events_table *table)
> > {
> > char *list_itr, *list_copy, *metric_name, *modifier;
> > @@ -1315,8 +1355,8 @@ static int metricgroup__add_metric_list(const char *list, bool metric_no_group,
> > *modifier++ = '\0';
> >
> > ret = metricgroup__add_metric(metric_name, modifier,
> > - metric_no_group, metric_list,
> > - table);
> > + metric_no_group, user_requested_cpu_list,
> > + system_wide, metric_list, table);
> > if (ret == -EINVAL)
> > pr_err("Cannot find metric or group `%s'\n", metric_name);
> >
> > @@ -1505,6 +1545,8 @@ static int parse_ids(bool metric_no_merge, struct perf_pmu *fake_pmu,
> > static int parse_groups(struct evlist *perf_evlist, const char *str,
> > bool metric_no_group,
> > bool metric_no_merge,
> > + const char *user_requested_cpu_list,
> > + bool system_wide,
> > struct perf_pmu *fake_pmu,
> > struct rblist *metric_events_list,
> > const struct pmu_events_table *table)
> > @@ -1518,7 +1560,8 @@ static int parse_groups(struct evlist *perf_evlist, const char *str,
> > if (metric_events_list->nr_entries == 0)
> > metricgroup__rblist_init(metric_events_list);
> > ret = metricgroup__add_metric_list(str, metric_no_group,
> > - &metric_list, table);
> > + user_requested_cpu_list,
> > + system_wide, &metric_list, table);
> > if (ret)
> > goto out;
> >
> > @@ -1650,6 +1693,8 @@ int metricgroup__parse_groups(struct evlist *perf_evlist,
> > const char *str,
> > bool metric_no_group,
> > bool metric_no_merge,
> > + const char *user_requested_cpu_list,
> > + bool system_wide,
> > struct rblist *metric_events)
> > {
> > const struct pmu_events_table *table = pmu_events_table__find();
> > @@ -1657,8 +1702,9 @@ int metricgroup__parse_groups(struct evlist *perf_evlist,
> > if (!table)
> > return -EINVAL;
> >
> > - return parse_groups(perf_evlist, str, metric_no_group,
> > - metric_no_merge, NULL, metric_events, table);
> > + return parse_groups(perf_evlist, str, metric_no_group, metric_no_merge,
> > + user_requested_cpu_list, system_wide,
> > + /*fake_pmu=*/NULL, metric_events, table);
> > }
> >
> > int metricgroup__parse_groups_test(struct evlist *evlist,
> > @@ -1668,8 +1714,10 @@ int metricgroup__parse_groups_test(struct evlist *evlist,
> > bool metric_no_merge,
> > struct rblist *metric_events)
> > {
> > - return parse_groups(evlist, str, metric_no_group,
> > - metric_no_merge, &perf_pmu__fake, metric_events, table);
> > + return parse_groups(evlist, str, metric_no_group, metric_no_merge,
> > + /*user_requested_cpu_list=*/NULL,
> > + /*system_wide=*/false,
> > + &perf_pmu__fake, metric_events, table);
> > }
> >
> > static int metricgroup__has_metric_callback(const struct pmu_event *pe,
> > diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h
> > index af9ceadaec0f..732d3a0d3334 100644
> > --- a/tools/perf/util/metricgroup.h
> > +++ b/tools/perf/util/metricgroup.h
> > @@ -68,6 +68,8 @@ int metricgroup__parse_groups(struct evlist *perf_evlist,
> > const char *str,
> > bool metric_no_group,
> > bool metric_no_merge,
> > + const char *user_requested_cpu_list,
> > + bool system_wide,
> > struct rblist *metric_events);
> > int metricgroup__parse_groups_test(struct evlist *evlist,
> > const struct pmu_events_table *table,
> > diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c
> > index 1439acd109db..1deb75741df4 100644
> > --- a/tools/perf/util/stat-shadow.c
> > +++ b/tools/perf/util/stat-shadow.c
> > @@ -911,7 +911,10 @@ static void generic_metric(struct perf_stat_config *config,
> > if (!pctx)
> > return;
> >
> > + if (config->user_requested_cpu_list)
> > + pctx->sctx.user_requested_cpu_list = strdup(config->user_requested_cpu_list);
> > pctx->sctx.runtime = runtime;
> > + pctx->sctx.system_wide = config->system_wide;
> > i = prepare_metric(metric_events, metric_refs, pctx, cpu_map_idx, st);
> > if (i < 0) {
> > expr__ctx_free(pctx);
> > @@ -1304,7 +1307,8 @@ void perf_stat__print_shadow_stats(struct perf_stat_config *config,
> > core_bound * 100.);
> > } else if (evsel->metric_expr) {
> > generic_metric(config, evsel->metric_expr, evsel->metric_events, NULL,
> > - evsel->name, evsel->metric_name, NULL, 1, cpu_map_idx, out, st);
> > + evsel->name, evsel->metric_name, NULL, 1,
> > + cpu_map_idx, out, st);
> > } else if (runtime_stat_n(st, STAT_NSECS, cpu_map_idx, &rsd) != 0) {
> > char unit = ' ';
> > char unit_buf[10] = "/sec";
> > @@ -1329,8 +1333,9 @@ void perf_stat__print_shadow_stats(struct perf_stat_config *config,
> > if (num++ > 0)
> > out->new_line(config, ctxp);
> > generic_metric(config, mexp->metric_expr, mexp->metric_events,
> > - mexp->metric_refs, evsel->name, mexp->metric_name,
> > - mexp->metric_unit, mexp->runtime, cpu_map_idx, out, st);
> > + mexp->metric_refs, evsel->name, mexp->metric_name,
> > + mexp->metric_unit, mexp->runtime,
> > + cpu_map_idx, out, st);
> > }
> > }
> > if (num == 0)
> > diff --git a/tools/perf/util/stat.h b/tools/perf/util/stat.h
> > index 668250022f8c..72713b344b79 100644
> > --- a/tools/perf/util/stat.h
> > +++ b/tools/perf/util/stat.h
> > @@ -141,6 +141,8 @@ struct perf_stat_config {
> > bool stop_read_counter;
> > bool quiet;
> > bool iostat_run;
> > + char *user_requested_cpu_list;
> > + bool system_wide;
> > FILE *output;
> > unsigned int interval;
> > unsigned int timeout;
> > --
> > 2.37.2.672.g94769d06f0-goog
>
> --
>
> - Arnaldo

2022-08-31 17:20:45

by Arnaldo Carvalho de Melo

[permalink] [raw]
Subject: Re: [PATCH v1 5/8] perf topology: Add core_wide

Em Wed, Aug 31, 2022 at 08:58:49AM -0700, Ian Rogers escreveu:
> On Wed, Aug 31, 2022 at 7:40 AM Arnaldo Carvalho de Melo
> <[email protected]> wrote:
> >
> > Em Tue, Aug 30, 2022 at 09:48:43AM -0700, Ian Rogers escreveu:
> > > It is possible to optimize metrics when all SMT threads (CPUs) on a
> > > core are measuring events in system wide mode. For example, TMA
> > > metrics defines CORE_CLKS for Sandybrdige as:
> > >
> > > if SMT is disabled:
> > > CPU_CLK_UNHALTED.THREAD
> > > if SMT is enabled and recording on all SMT threads:
> > > CPU_CLK_UNHALTED.THREAD_ANY / 2
> > > if SMT is enabled and not recording on all SMT threads:
> > > (CPU_CLK_UNHALTED.THREAD/2)*
> > > (1+CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE/CPU_CLK_UNHALTED.REF_XCLK )
> > >
> > > That is two more events are necessary when not gathering counts on all
> > > SMT threads. To distinguish all SMT threads on a core vs system wide
> > > (all CPUs) call the new property core wide. Add a core wide test that
> > > determines the property from user requested CPUs, the topology and
> > > system wide. System wide is required as other processes running on a
> > > SMT thread will change the counts.
> > >
> > > Signed-off-by: Ian Rogers <[email protected]>
> > > ---
> > > tools/perf/util/cputopo.c | 46 +++++++++++++++++++++++++++++++++++++++
> > > tools/perf/util/cputopo.h | 3 +++
> > > tools/perf/util/smt.c | 14 ++++++++++++
> > > tools/perf/util/smt.h | 7 ++++++
> > > 4 files changed, 70 insertions(+)
> > >
> > > diff --git a/tools/perf/util/cputopo.c b/tools/perf/util/cputopo.c
> > > index 511002e52714..1a3ff6449158 100644
> > > --- a/tools/perf/util/cputopo.c
> > > +++ b/tools/perf/util/cputopo.c
> > > @@ -172,6 +172,52 @@ bool cpu_topology__smt_on(const struct cpu_topology *topology)
> > > return false;
> > > }
> > >
> > > +bool cpu_topology__core_wide(const struct cpu_topology *topology,
> > > + const char *user_requested_cpu_list)
> > > +{
> > > + struct perf_cpu_map *user_requested_cpus;
> > > +
> > > + /*
> > > + * If user_requested_cpu_list is empty then all CPUs are recorded and so
> > > + * core_wide is true.
> > > + */
> > > + if (!user_requested_cpu_list)
> > > + return true;
> > > +
> > > + user_requested_cpus = perf_cpu_map__new(user_requested_cpu_list);
> >
> > Don't we need a NULL test here?

> No, NULL is a valid perf_cpu_map, one with no values in it. For
> example, see here:

But in this specific case, if perf_cpu_map__new() returns NULL, its
because it failed to parse user_requested_cpu_list, so checking against
NULL is valid, no?

- Arnaldo

> https://git.kernel.org/pub/scm/linux/kernel/git/acme/linux.git/tree/tools/lib/perf/cpumap.c?h=perf/core#n266
> So there's no way to tell an error (ENOMEM) from an empty map. My
> suggestion is a rewrite of perf_cpu_map, I'd like to do this in a new
> "libperf2" which has a more permissive than GPL license like libbpf.
> That is out-of-scope here.

> Thanks,
> Ian
>
> > > + /* Check that every user requested CPU is the complete set of SMT threads on a core. */
> > > + for (u32 i = 0; i < topology->core_cpus_lists; i++) {
> > > + const char *core_cpu_list = topology->core_cpus_list[i];
> > > + struct perf_cpu_map *core_cpus = perf_cpu_map__new(core_cpu_list);
> >
> > Here too, no?
> >
> > > + struct perf_cpu cpu;
> > > + int idx;
> > > + bool has_first, first = true;
> > > +
> > > + perf_cpu_map__for_each_cpu(cpu, idx, core_cpus) {
> > > + if (first) {
> > > + has_first = perf_cpu_map__has(user_requested_cpus, cpu);
> > > + first = false;
> > > + } else {
> > > + /*
> > > + * If the first core CPU is user requested then
> > > + * all subsequent CPUs in the core must be user
> > > + * requested too. If the first CPU isn't user
> > > + * requested then none of the others must be
> > > + * too.
> > > + */
> > > + if (perf_cpu_map__has(user_requested_cpus, cpu) != has_first) {
> > > + perf_cpu_map__put(core_cpus);
> > > + perf_cpu_map__put(user_requested_cpus);
> > > + return false;
> > > + }
> > > + }
> > > + }
> > > + perf_cpu_map__put(core_cpus);
> > > + }
> > > + perf_cpu_map__put(user_requested_cpus);
> > > + return true;
> > > +}
> > > +
> > > static bool has_die_topology(void)
> > > {
> > > char filename[MAXPATHLEN];
> > > diff --git a/tools/perf/util/cputopo.h b/tools/perf/util/cputopo.h
> > > index 469db775a13c..969e5920a00e 100644
> > > --- a/tools/perf/util/cputopo.h
> > > +++ b/tools/perf/util/cputopo.h
> > > @@ -60,6 +60,9 @@ struct cpu_topology *cpu_topology__new(void);
> > > void cpu_topology__delete(struct cpu_topology *tp);
> > > /* Determine from the core list whether SMT was enabled. */
> > > bool cpu_topology__smt_on(const struct cpu_topology *topology);
> > > +/* Are the sets of SMT siblings all enabled or all disabled in user_requested_cpus. */
> > > +bool cpu_topology__core_wide(const struct cpu_topology *topology,
> > > + const char *user_requested_cpu_list);
> > >
> > > struct numa_topology *numa_topology__new(void);
> > > void numa_topology__delete(struct numa_topology *tp);
> > > diff --git a/tools/perf/util/smt.c b/tools/perf/util/smt.c
> > > index ce90c4ee4138..994e9e418227 100644
> > > --- a/tools/perf/util/smt.c
> > > +++ b/tools/perf/util/smt.c
> > > @@ -21,3 +21,17 @@ bool smt_on(const struct cpu_topology *topology)
> > > cached = true;
> > > return cached_result;
> > > }
> > > +
> > > +bool core_wide(bool system_wide, const char *user_requested_cpu_list,
> > > + const struct cpu_topology *topology)
> > > +{
> > > + /* If not everything running on a core is being recorded then we can't use core_wide. */
> > > + if (!system_wide)
> > > + return false;
> > > +
> > > + /* Cheap case that SMT is disabled and therefore we're inherently core_wide. */
> > > + if (!smt_on(topology))
> > > + return true;
> > > +
> > > + return cpu_topology__core_wide(topology, user_requested_cpu_list);
> > > +}
> > > diff --git a/tools/perf/util/smt.h b/tools/perf/util/smt.h
> > > index e26999c6b8d4..ae9095f2c38c 100644
> > > --- a/tools/perf/util/smt.h
> > > +++ b/tools/perf/util/smt.h
> > > @@ -7,4 +7,11 @@ struct cpu_topology;
> > > /* Returns true if SMT (aka hyperthreading) is enabled. */
> > > bool smt_on(const struct cpu_topology *topology);
> > >
> > > +/*
> > > + * Returns true when system wide and all SMT threads for a core are in the
> > > + * user_requested_cpus map.
> > > + */
> > > +bool core_wide(bool system_wide, const char *user_requested_cpu_list,
> > > + const struct cpu_topology *topology);
> > > +
> > > #endif /* __SMT_H */
> > > --
> > > 2.37.2.672.g94769d06f0-goog
> >
> > --
> >
> > - Arnaldo

--

- Arnaldo

2022-08-31 17:21:24

by Ian Rogers

[permalink] [raw]
Subject: Re: [PATCH v1 5/8] perf topology: Add core_wide

On Wed, Aug 31, 2022 at 9:20 AM Arnaldo Carvalho de Melo
<[email protected]> wrote:
>
> Em Wed, Aug 31, 2022 at 08:58:49AM -0700, Ian Rogers escreveu:
> > On Wed, Aug 31, 2022 at 7:40 AM Arnaldo Carvalho de Melo
> > <[email protected]> wrote:
> > >
> > > Em Tue, Aug 30, 2022 at 09:48:43AM -0700, Ian Rogers escreveu:
> > > > It is possible to optimize metrics when all SMT threads (CPUs) on a
> > > > core are measuring events in system wide mode. For example, TMA
> > > > metrics defines CORE_CLKS for Sandybrdige as:
> > > >
> > > > if SMT is disabled:
> > > > CPU_CLK_UNHALTED.THREAD
> > > > if SMT is enabled and recording on all SMT threads:
> > > > CPU_CLK_UNHALTED.THREAD_ANY / 2
> > > > if SMT is enabled and not recording on all SMT threads:
> > > > (CPU_CLK_UNHALTED.THREAD/2)*
> > > > (1+CPU_CLK_UNHALTED.ONE_THREAD_ACTIVE/CPU_CLK_UNHALTED.REF_XCLK )
> > > >
> > > > That is two more events are necessary when not gathering counts on all
> > > > SMT threads. To distinguish all SMT threads on a core vs system wide
> > > > (all CPUs) call the new property core wide. Add a core wide test that
> > > > determines the property from user requested CPUs, the topology and
> > > > system wide. System wide is required as other processes running on a
> > > > SMT thread will change the counts.
> > > >
> > > > Signed-off-by: Ian Rogers <[email protected]>
> > > > ---
> > > > tools/perf/util/cputopo.c | 46 +++++++++++++++++++++++++++++++++++++++
> > > > tools/perf/util/cputopo.h | 3 +++
> > > > tools/perf/util/smt.c | 14 ++++++++++++
> > > > tools/perf/util/smt.h | 7 ++++++
> > > > 4 files changed, 70 insertions(+)
> > > >
> > > > diff --git a/tools/perf/util/cputopo.c b/tools/perf/util/cputopo.c
> > > > index 511002e52714..1a3ff6449158 100644
> > > > --- a/tools/perf/util/cputopo.c
> > > > +++ b/tools/perf/util/cputopo.c
> > > > @@ -172,6 +172,52 @@ bool cpu_topology__smt_on(const struct cpu_topology *topology)
> > > > return false;
> > > > }
> > > >
> > > > +bool cpu_topology__core_wide(const struct cpu_topology *topology,
> > > > + const char *user_requested_cpu_list)
> > > > +{
> > > > + struct perf_cpu_map *user_requested_cpus;
> > > > +
> > > > + /*
> > > > + * If user_requested_cpu_list is empty then all CPUs are recorded and so
> > > > + * core_wide is true.
> > > > + */
> > > > + if (!user_requested_cpu_list)
> > > > + return true;
> > > > +
> > > > + user_requested_cpus = perf_cpu_map__new(user_requested_cpu_list);
> > >
> > > Don't we need a NULL test here?
>
> > No, NULL is a valid perf_cpu_map, one with no values in it. For
> > example, see here:
>
> But in this specific case, if perf_cpu_map__new() returns NULL, its
> because it failed to parse user_requested_cpu_list, so checking against
> NULL is valid, no?

What about the empty string?

Thanks,
Ian

> - Arnaldo
>
> > https://git.kernel.org/pub/scm/linux/kernel/git/acme/linux.git/tree/tools/lib/perf/cpumap.c?h=perf/core#n266
> > So there's no way to tell an error (ENOMEM) from an empty map. My
> > suggestion is a rewrite of perf_cpu_map, I'd like to do this in a new
> > "libperf2" which has a more permissive than GPL license like libbpf.
> > That is out-of-scope here.
>
> > Thanks,
> > Ian
> >
> > > > + /* Check that every user requested CPU is the complete set of SMT threads on a core. */
> > > > + for (u32 i = 0; i < topology->core_cpus_lists; i++) {
> > > > + const char *core_cpu_list = topology->core_cpus_list[i];
> > > > + struct perf_cpu_map *core_cpus = perf_cpu_map__new(core_cpu_list);
> > >
> > > Here too, no?
> > >
> > > > + struct perf_cpu cpu;
> > > > + int idx;
> > > > + bool has_first, first = true;
> > > > +
> > > > + perf_cpu_map__for_each_cpu(cpu, idx, core_cpus) {
> > > > + if (first) {
> > > > + has_first = perf_cpu_map__has(user_requested_cpus, cpu);
> > > > + first = false;
> > > > + } else {
> > > > + /*
> > > > + * If the first core CPU is user requested then
> > > > + * all subsequent CPUs in the core must be user
> > > > + * requested too. If the first CPU isn't user
> > > > + * requested then none of the others must be
> > > > + * too.
> > > > + */
> > > > + if (perf_cpu_map__has(user_requested_cpus, cpu) != has_first) {
> > > > + perf_cpu_map__put(core_cpus);
> > > > + perf_cpu_map__put(user_requested_cpus);
> > > > + return false;
> > > > + }
> > > > + }
> > > > + }
> > > > + perf_cpu_map__put(core_cpus);
> > > > + }
> > > > + perf_cpu_map__put(user_requested_cpus);
> > > > + return true;
> > > > +}
> > > > +
> > > > static bool has_die_topology(void)
> > > > {
> > > > char filename[MAXPATHLEN];
> > > > diff --git a/tools/perf/util/cputopo.h b/tools/perf/util/cputopo.h
> > > > index 469db775a13c..969e5920a00e 100644
> > > > --- a/tools/perf/util/cputopo.h
> > > > +++ b/tools/perf/util/cputopo.h
> > > > @@ -60,6 +60,9 @@ struct cpu_topology *cpu_topology__new(void);
> > > > void cpu_topology__delete(struct cpu_topology *tp);
> > > > /* Determine from the core list whether SMT was enabled. */
> > > > bool cpu_topology__smt_on(const struct cpu_topology *topology);
> > > > +/* Are the sets of SMT siblings all enabled or all disabled in user_requested_cpus. */
> > > > +bool cpu_topology__core_wide(const struct cpu_topology *topology,
> > > > + const char *user_requested_cpu_list);
> > > >
> > > > struct numa_topology *numa_topology__new(void);
> > > > void numa_topology__delete(struct numa_topology *tp);
> > > > diff --git a/tools/perf/util/smt.c b/tools/perf/util/smt.c
> > > > index ce90c4ee4138..994e9e418227 100644
> > > > --- a/tools/perf/util/smt.c
> > > > +++ b/tools/perf/util/smt.c
> > > > @@ -21,3 +21,17 @@ bool smt_on(const struct cpu_topology *topology)
> > > > cached = true;
> > > > return cached_result;
> > > > }
> > > > +
> > > > +bool core_wide(bool system_wide, const char *user_requested_cpu_list,
> > > > + const struct cpu_topology *topology)
> > > > +{
> > > > + /* If not everything running on a core is being recorded then we can't use core_wide. */
> > > > + if (!system_wide)
> > > > + return false;
> > > > +
> > > > + /* Cheap case that SMT is disabled and therefore we're inherently core_wide. */
> > > > + if (!smt_on(topology))
> > > > + return true;
> > > > +
> > > > + return cpu_topology__core_wide(topology, user_requested_cpu_list);
> > > > +}
> > > > diff --git a/tools/perf/util/smt.h b/tools/perf/util/smt.h
> > > > index e26999c6b8d4..ae9095f2c38c 100644
> > > > --- a/tools/perf/util/smt.h
> > > > +++ b/tools/perf/util/smt.h
> > > > @@ -7,4 +7,11 @@ struct cpu_topology;
> > > > /* Returns true if SMT (aka hyperthreading) is enabled. */
> > > > bool smt_on(const struct cpu_topology *topology);
> > > >
> > > > +/*
> > > > + * Returns true when system wide and all SMT threads for a core are in the
> > > > + * user_requested_cpus map.
> > > > + */
> > > > +bool core_wide(bool system_wide, const char *user_requested_cpu_list,
> > > > + const struct cpu_topology *topology);
> > > > +
> > > > #endif /* __SMT_H */
> > > > --
> > > > 2.37.2.672.g94769d06f0-goog
> > >
> > > --
> > >
> > > - Arnaldo
>
> --
>
> - Arnaldo