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:
parent
9b0a783635
commit
bb1c15b60b
@ -168,8 +168,9 @@ command line can be used: 'perf stat -e cycles -G cgroup_name -a -e cycles'.
|
||||
|
||||
--for-each-cgroup name::
|
||||
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
|
||||
each event x name. This option cannot be used with -G/--cgroup option.
|
||||
by comma). It also support regex patterns to match multiple groups. This has same
|
||||
effect that repeating -e option and -G option for each event x name. This option
|
||||
cannot be used with -G/--cgroup option.
|
||||
|
||||
-o file::
|
||||
--output file::
|
||||
|
@ -2235,8 +2235,11 @@ int cmd_stat(int argc, const char **argv)
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
target__validate(&target);
|
||||
|
@ -13,9 +13,19 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <api/fs/fs.h>
|
||||
#include <ftw.h>
|
||||
#include <regex.h>
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
/* 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(®, 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(®, name, 0, NULL, 0))
|
||||
cn->used = true;
|
||||
}
|
||||
regfree(®);
|
||||
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 unset __maybe_unused)
|
||||
{
|
||||
@ -201,6 +342,11 @@ int parse_cgroups(const struct option *opt, const char *str,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool has_pattern_string(const char *str)
|
||||
{
|
||||
return !!strpbrk(str, "{}[]()|*+?^$");
|
||||
}
|
||||
|
||||
int evlist__expand_cgroup(struct evlist *evlist, const char *str,
|
||||
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 rblist orig_metric_events;
|
||||
struct cgroup *cgrp = NULL;
|
||||
const char *p, *e, *eos = str + strlen(str);
|
||||
struct cgroup_name *cn;
|
||||
int ret = -1;
|
||||
int prefix_len;
|
||||
|
||||
if (evlist->core.nr_entries == 0) {
|
||||
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);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
p = strchr(str, ',');
|
||||
e = p ? p : eos;
|
||||
if (has_pattern_string(str))
|
||||
prefix_len = match_cgroups(str);
|
||||
else
|
||||
prefix_len = list_cgroups(str);
|
||||
|
||||
/* allow empty cgroups, i.e., skip */
|
||||
if (e - str) {
|
||||
/* termination added */
|
||||
char *name = strndup(str, e - str);
|
||||
if (!name)
|
||||
goto out_err;
|
||||
if (prefix_len < 0)
|
||||
goto out_err;
|
||||
|
||||
cgrp = cgroup__new(name, open_cgroup);
|
||||
free(name);
|
||||
if (cgrp == NULL)
|
||||
goto out_err;
|
||||
} else {
|
||||
cgrp = NULL;
|
||||
}
|
||||
list_for_each_entry(cn, &cgroup_list, list) {
|
||||
char *name;
|
||||
|
||||
if (!cn->used)
|
||||
continue;
|
||||
|
||||
/* 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;
|
||||
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,
|
||||
metric_events,
|
||||
&orig_metric_events) < 0)
|
||||
break;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
perf_evlist__splice_list_tail(evlist, &tmp_list->core.entries);
|
||||
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:
|
||||
evlist__delete(orig_list);
|
||||
evlist__delete(tmp_list);
|
||||
rblist__exit(&orig_metric_events);
|
||||
release_cgroup_list();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user