perf stat: Support regex pattern in --for-each-cgroup

To make the command line even more compact with cgroups, support regex
pattern matching in cgroup names.

  $ perf stat -a -e cpu-clock,cycles --for-each-cgroup ^foo sleep 1

          3,000.73 msec cpu-clock                 foo #    2.998 CPUs utilized
    12,530,992,699      cycles                    foo #    7.517 GHz                      (100.00%)
          1,000.61 msec cpu-clock                 foo/bar #    1.000 CPUs utilized
     4,178,529,579      cycles                    foo/bar #    2.506 GHz                      (100.00%)
          1,000.03 msec cpu-clock                 foo/baz #    0.999 CPUs utilized
     4,176,104,315      cycles                    foo/baz #    2.505 GHz                      (100.00%)

       1.000892614 seconds time elapsed

Signed-off-by: Namhyung Kim <namhyung@kernel.org>
Acked-by: Jiri Olsa <jolsa@redhat.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: Ian Rogers <irogers@google.com>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Stephane Eranian <eranian@google.com>
Link: http://lore.kernel.org/lkml/20201027072855.655449-2-namhyung@kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
This commit is contained in:
Namhyung Kim 2020-10-27 16:28:55 +09:00 committed by Arnaldo Carvalho de Melo
parent 9b0a783635
commit bb1c15b60b
3 changed files with 183 additions and 27 deletions

View File

@ -168,8 +168,9 @@ command line can be used: 'perf stat -e cycles -G cgroup_name -a -e cycles'.
--for-each-cgroup name:: --for-each-cgroup name::
Expand event list for each cgroup in "name" (allow multiple cgroups separated Expand event list for each cgroup in "name" (allow multiple cgroups separated
by comma). This has same effect that repeating -e option and -G option for by comma). It also support regex patterns to match multiple groups. This has same
each event x name. This option cannot be used with -G/--cgroup option. effect that repeating -e option and -G option for each event x name. This option
cannot be used with -G/--cgroup option.
-o file:: -o file::
--output file:: --output file::

View File

@ -2235,8 +2235,11 @@ int cmd_stat(int argc, const char **argv)
} }
if (evlist__expand_cgroup(evsel_list, stat_config.cgroup_list, if (evlist__expand_cgroup(evsel_list, stat_config.cgroup_list,
&stat_config.metric_events, true) < 0) &stat_config.metric_events, true) < 0) {
parse_options_usage(stat_usage, stat_options,
"for-each-cgroup", 0);
goto out; goto out;
}
} }
target__validate(&target); target__validate(&target);

View File

@ -13,9 +13,19 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <api/fs/fs.h> #include <api/fs/fs.h>
#include <ftw.h>
#include <regex.h>
int nr_cgroups; int nr_cgroups;
/* used to match cgroup name with patterns */
struct cgroup_name {
struct list_head list;
bool used;
char name[];
};
static LIST_HEAD(cgroup_list);
static int open_cgroup(const char *name) static int open_cgroup(const char *name)
{ {
char path[PATH_MAX + 1]; char path[PATH_MAX + 1];
@ -149,6 +159,137 @@ void evlist__set_default_cgroup(struct evlist *evlist, struct cgroup *cgroup)
evsel__set_default_cgroup(evsel, cgroup); evsel__set_default_cgroup(evsel, cgroup);
} }
/* helper function for ftw() in match_cgroups and list_cgroups */
static int add_cgroup_name(const char *fpath, const struct stat *sb __maybe_unused,
int typeflag)
{
struct cgroup_name *cn;
if (typeflag != FTW_D)
return 0;
cn = malloc(sizeof(*cn) + strlen(fpath) + 1);
if (cn == NULL)
return -1;
cn->used = false;
strcpy(cn->name, fpath);
list_add_tail(&cn->list, &cgroup_list);
return 0;
}
static void release_cgroup_list(void)
{
struct cgroup_name *cn;
while (!list_empty(&cgroup_list)) {
cn = list_first_entry(&cgroup_list, struct cgroup_name, list);
list_del(&cn->list);
free(cn);
}
}
/* collect given cgroups only */
static int list_cgroups(const char *str)
{
const char *p, *e, *eos = str + strlen(str);
struct cgroup_name *cn;
char *s;
/* use given name as is - for testing purpose */
for (;;) {
p = strchr(str, ',');
e = p ? p : eos;
if (e - str) {
int ret;
s = strndup(str, e - str);
if (!s)
return -1;
/* pretend if it's added by ftw() */
ret = add_cgroup_name(s, NULL, FTW_D);
free(s);
if (ret)
return -1;
} else {
if (add_cgroup_name("", NULL, FTW_D) < 0)
return -1;
}
if (!p)
break;
str = p+1;
}
/* these groups will be used */
list_for_each_entry(cn, &cgroup_list, list)
cn->used = true;
return 0;
}
/* collect all cgroups first and then match with the pattern */
static int match_cgroups(const char *str)
{
char mnt[PATH_MAX];
const char *p, *e, *eos = str + strlen(str);
struct cgroup_name *cn;
regex_t reg;
int prefix_len;
char *s;
if (cgroupfs_find_mountpoint(mnt, sizeof(mnt), "perf_event"))
return -1;
/* cgroup_name will have a full path, skip the root directory */
prefix_len = strlen(mnt);
/* collect all cgroups in the cgroup_list */
if (ftw(mnt, add_cgroup_name, 20) < 0)
return -1;
for (;;) {
p = strchr(str, ',');
e = p ? p : eos;
/* allow empty cgroups, i.e., skip */
if (e - str) {
/* termination added */
s = strndup(str, e - str);
if (!s)
return -1;
if (regcomp(&reg, s, REG_NOSUB)) {
free(s);
return -1;
}
/* check cgroup name with the pattern */
list_for_each_entry(cn, &cgroup_list, list) {
char *name = cn->name + prefix_len;
if (name[0] == '/' && name[1])
name++;
if (!regexec(&reg, name, 0, NULL, 0))
cn->used = true;
}
regfree(&reg);
free(s);
} else {
/* first entry to root cgroup */
cn = list_first_entry(&cgroup_list, struct cgroup_name,
list);
cn->used = true;
}
if (!p)
break;
str = p+1;
}
return prefix_len;
}
int parse_cgroups(const struct option *opt, const char *str, int parse_cgroups(const struct option *opt, const char *str,
int unset __maybe_unused) int unset __maybe_unused)
{ {
@ -201,6 +342,11 @@ int parse_cgroups(const struct option *opt, const char *str,
return 0; return 0;
} }
static bool has_pattern_string(const char *str)
{
return !!strpbrk(str, "{}[]()|*+?^$");
}
int evlist__expand_cgroup(struct evlist *evlist, const char *str, int evlist__expand_cgroup(struct evlist *evlist, const char *str,
struct rblist *metric_events, bool open_cgroup) struct rblist *metric_events, bool open_cgroup)
{ {
@ -208,8 +354,9 @@ int evlist__expand_cgroup(struct evlist *evlist, const char *str,
struct evsel *pos, *evsel, *leader; struct evsel *pos, *evsel, *leader;
struct rblist orig_metric_events; struct rblist orig_metric_events;
struct cgroup *cgrp = NULL; struct cgroup *cgrp = NULL;
const char *p, *e, *eos = str + strlen(str); struct cgroup_name *cn;
int ret = -1; int ret = -1;
int prefix_len;
if (evlist->core.nr_entries == 0) { if (evlist->core.nr_entries == 0) {
fprintf(stderr, "must define events before cgroups\n"); fprintf(stderr, "must define events before cgroups\n");
@ -234,24 +381,27 @@ int evlist__expand_cgroup(struct evlist *evlist, const char *str,
rblist__init(&orig_metric_events); rblist__init(&orig_metric_events);
} }
for (;;) { if (has_pattern_string(str))
p = strchr(str, ','); prefix_len = match_cgroups(str);
e = p ? p : eos; else
prefix_len = list_cgroups(str);
/* allow empty cgroups, i.e., skip */ if (prefix_len < 0)
if (e - str) { goto out_err;
/* termination added */
char *name = strndup(str, e - str);
if (!name)
goto out_err;
cgrp = cgroup__new(name, open_cgroup); list_for_each_entry(cn, &cgroup_list, list) {
free(name); char *name;
if (cgrp == NULL)
goto out_err; if (!cn->used)
} else { continue;
cgrp = NULL;
} /* cgroup_name might have a full path, skip the prefix */
name = cn->name + prefix_len;
if (name[0] == '/' && name[1])
name++;
cgrp = cgroup__new(name, open_cgroup);
if (cgrp == NULL)
goto out_err;
leader = NULL; leader = NULL;
evlist__for_each_entry(orig_list, pos) { evlist__for_each_entry(orig_list, pos) {
@ -277,23 +427,25 @@ int evlist__expand_cgroup(struct evlist *evlist, const char *str,
if (metricgroup__copy_metric_events(tmp_list, cgrp, if (metricgroup__copy_metric_events(tmp_list, cgrp,
metric_events, metric_events,
&orig_metric_events) < 0) &orig_metric_events) < 0)
break; goto out_err;
} }
perf_evlist__splice_list_tail(evlist, &tmp_list->core.entries); perf_evlist__splice_list_tail(evlist, &tmp_list->core.entries);
tmp_list->core.nr_entries = 0; tmp_list->core.nr_entries = 0;
if (!p) {
ret = 0;
break;
}
str = p+1;
} }
if (list_empty(&evlist->core.entries)) {
fprintf(stderr, "no cgroup matched: %s\n", str);
goto out_err;
}
ret = 0;
out_err: out_err:
evlist__delete(orig_list); evlist__delete(orig_list);
evlist__delete(tmp_list); evlist__delete(tmp_list);
rblist__exit(&orig_metric_events); rblist__exit(&orig_metric_events);
release_cgroup_list();
return ret; return ret;
} }