perf test pmu: Refactor format test and exposed test APIs
authorIan Rogers <irogers@google.com>
Thu, 2 May 2024 21:35:04 +0000 (14:35 -0700)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Fri, 3 May 2024 20:08:20 +0000 (17:08 -0300)
In tests/pmu.c, make a common utility that creates a PMU in a mkdtemp
directory and uses regular PMU parsing logic to load that PMU. Formats
must still be eagerly loaded as by default the PMU code assumes devices
are going to be in sysfs.

In util/pmu.[ch], hide perf_pmu__format_parse but add the eager argument
to perf_pmu__lookup called by perf_pmus__add_test_pmu. Later patches
will eagerly load other non-sysfs files when eager loading is enabled.

In tests/pmu.c, rather than manually constructing a list of term
arguments, just use the term parsing code from a string.

Add more comments and debug logging.

Reviewed-by: Kan Liang <kan.liang@linux.intel.com>
Signed-off-by: Ian Rogers <irogers@google.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Bjorn Helgaas <bhelgaas@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Clark <james.clark@arm.com>
Cc: Jing Zhang <renyu.zj@linux.alibaba.com>
Cc: Jiri Olsa <jolsa@kernel.org>
Cc: Jonathan Corbet <corbet@lwn.net>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Randy Dunlap <rdunlap@infradead.org>
Cc: Ravi Bangoria <ravi.bangoria@amd.com>
Cc: Thomas Richter <tmricht@linux.ibm.com>
Link: https://lore.kernel.org/r/20240502213507.2339733-4-irogers@google.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
tools/perf/tests/pmu.c
tools/perf/util/parse-events.c
tools/perf/util/parse-events.h
tools/perf/util/pmu.c
tools/perf/util/pmu.h
tools/perf/util/pmus.c
tools/perf/util/pmus.h

index 8f18127d876a7d528c624fb997d7fb53255b014f..424ebdb0f09da98705e59a587d1e42408a85bfe3 100644 (file)
 #include "parse-events.h"
 #include "pmu.h"
 #include "tests.h"
+#include "debug.h"
 #include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
-#include <linux/kernel.h>
-#include <linux/limits.h>
-#include <linux/zalloc.h>
-
-/* Simulated format definitions. */
-static struct test_format {
-       const char *name;
-       const char *value;
-} test_formats[] = {
-       { "krava01", "config:0-1,62-63\n", },
-       { "krava02", "config:10-17\n", },
-       { "krava03", "config:5\n", },
-       { "krava11", "config1:0,2,4,6,8,20-28\n", },
-       { "krava12", "config1:63\n", },
-       { "krava13", "config1:45-47\n", },
-       { "krava21", "config2:0-3,10-13,20-23,30-33,40-43,50-53,60-63\n", },
-       { "krava22", "config2:8,18,48,58\n", },
-       { "krava23", "config2:28-29,38\n", },
-};
-
-/* Simulated users input. */
-static struct parse_events_term test_terms[] = {
-       {
-               .config    = "krava01",
-               .val.num   = 15,
-               .type_val  = PARSE_EVENTS__TERM_TYPE_NUM,
-               .type_term = PARSE_EVENTS__TERM_TYPE_USER,
-       },
-       {
-               .config    = "krava02",
-               .val.num   = 170,
-               .type_val  = PARSE_EVENTS__TERM_TYPE_NUM,
-               .type_term = PARSE_EVENTS__TERM_TYPE_USER,
-       },
-       {
-               .config    = "krava03",
-               .val.num   = 1,
-               .type_val  = PARSE_EVENTS__TERM_TYPE_NUM,
-               .type_term = PARSE_EVENTS__TERM_TYPE_USER,
-       },
-       {
-               .config    = "krava11",
-               .val.num   = 27,
-               .type_val  = PARSE_EVENTS__TERM_TYPE_NUM,
-               .type_term = PARSE_EVENTS__TERM_TYPE_USER,
-       },
-       {
-               .config    = "krava12",
-               .val.num   = 1,
-               .type_val  = PARSE_EVENTS__TERM_TYPE_NUM,
-               .type_term = PARSE_EVENTS__TERM_TYPE_USER,
-       },
-       {
-               .config    = "krava13",
-               .val.num   = 2,
-               .type_val  = PARSE_EVENTS__TERM_TYPE_NUM,
-               .type_term = PARSE_EVENTS__TERM_TYPE_USER,
-       },
-       {
-               .config    = "krava21",
-               .val.num   = 119,
-               .type_val  = PARSE_EVENTS__TERM_TYPE_NUM,
-               .type_term = PARSE_EVENTS__TERM_TYPE_USER,
-       },
-       {
-               .config    = "krava22",
-               .val.num   = 11,
-               .type_val  = PARSE_EVENTS__TERM_TYPE_NUM,
-               .type_term = PARSE_EVENTS__TERM_TYPE_USER,
-       },
-       {
-               .config    = "krava23",
-               .val.num   = 2,
-               .type_val  = PARSE_EVENTS__TERM_TYPE_NUM,
-               .type_term = PARSE_EVENTS__TERM_TYPE_USER,
-       },
-};
-
-/*
- * Prepare format directory data, exported by kernel
- * at /sys/bus/event_source/devices/<dev>/format.
- */
-static char *test_format_dir_get(char *dir, size_t sz)
-{
-       unsigned int i;
-
-       snprintf(dir, sz, "/tmp/perf-pmu-test-format-XXXXXX");
-       if (!mkdtemp(dir))
-               return NULL;
-
-       for (i = 0; i < ARRAY_SIZE(test_formats); i++) {
-               char name[PATH_MAX];
-               struct test_format *format = &test_formats[i];
-               FILE *file;
-
-               scnprintf(name, PATH_MAX, "%s/%s", dir, format->name);
-
-               file = fopen(name, "w");
-               if (!file)
-                       return NULL;
-
-               if (1 != fwrite(format->value, strlen(format->value), 1, file))
-                       break;
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
 
-               fclose(file);
-       }
-
-       return dir;
-}
+/* Fake PMUs created in temp directory. */
+static LIST_HEAD(test_pmus);
 
-/* Cleanup format directory. */
-static int test_format_dir_put(char *dir)
+/* Cleanup test PMU directory. */
+static int test_pmu_put(const char *dir, struct perf_pmu *pmu)
 {
        char buf[PATH_MAX + 20];
+       int ret;
 
-       snprintf(buf, sizeof(buf), "rm -f %s/*\n", dir);
-       if (system(buf))
-               return -1;
+       if (scnprintf(buf, sizeof(buf), "rm -fr %s", dir) < 0) {
+               pr_err("Failure to set up buffer for \"%s\"\n", dir);
+               return -EINVAL;
+       }
+       ret = system(buf);
+       if (ret)
+               pr_err("Failure to \"%s\"\n", buf);
 
-       snprintf(buf, sizeof(buf), "rmdir %s\n", dir);
-       return system(buf);
+       list_del(&pmu->list);
+       perf_pmu__delete(pmu);
+       return ret;
 }
 
-static void add_test_terms(struct parse_events_terms *terms)
+/*
+ * Prepare test PMU directory data, normally exported by kernel at
+ * /sys/bus/event_source/devices/<pmu>/. Give as input a buffer to hold the file
+ * path, the result is PMU loaded using that directory.
+ */
+static struct perf_pmu *test_pmu_get(char *dir, size_t sz)
 {
-       unsigned int i;
+       /* Simulated format definitions. */
+       const struct test_format {
+               const char *name;
+               const char *value;
+       } test_formats[] = {
+               { "krava01", "config:0-1,62-63\n", },
+               { "krava02", "config:10-17\n", },
+               { "krava03", "config:5\n", },
+               { "krava11", "config1:0,2,4,6,8,20-28\n", },
+               { "krava12", "config1:63\n", },
+               { "krava13", "config1:45-47\n", },
+               { "krava21", "config2:0-3,10-13,20-23,30-33,40-43,50-53,60-63\n", },
+               { "krava22", "config2:8,18,48,58\n", },
+               { "krava23", "config2:28-29,38\n", },
+       };
+       char name[PATH_MAX];
+       int dirfd, file;
+       struct perf_pmu *pmu = NULL;
+       ssize_t len;
+
+       /* Create equivalent of sysfs mount point. */
+       scnprintf(dir, sz, "/tmp/perf-pmu-test-XXXXXX");
+       if (!mkdtemp(dir)) {
+               pr_err("mkdtemp failed\n");
+               dir[0] = '\0';
+               return NULL;
+       }
+       dirfd = open(dir, O_DIRECTORY);
+       if (dirfd < 0) {
+               pr_err("Failed to open test directory \"%s\"\n", dir);
+               goto err_out;
+       }
 
-       for (i = 0; i < ARRAY_SIZE(test_terms); i++) {
-               struct parse_events_term *clone;
+       /* Create the test PMU directory and give it a perf_event_attr type number. */
+       if (mkdirat(dirfd, "perf-pmu-test", 0755) < 0) {
+               pr_err("Failed to mkdir PMU directory\n");
+               goto err_out;
+       }
+       file = openat(dirfd, "perf-pmu-test/type", O_WRONLY | O_CREAT, 0600);
+       if (!file) {
+               pr_err("Failed to open for writing file \"type\"\n");
+               goto err_out;
+       }
+       len = strlen("9999");
+       if (write(file, "9999\n", len) < len) {
+               close(file);
+               pr_err("Failed to write to 'type' file\n");
+               goto err_out;
+       }
+       close(file);
 
-               parse_events_term__clone(&clone, &test_terms[i]);
-               list_add_tail(&clone->list, &terms->terms);
+       /* Create format directory and files. */
+       if (mkdirat(dirfd, "perf-pmu-test/format", 0755) < 0) {
+               pr_err("Failed to mkdir PMU format directory\n)");
+               goto err_out;
        }
+       for (size_t i = 0; i < ARRAY_SIZE(test_formats); i++) {
+               const struct test_format *format = &test_formats[i];
+
+               if (scnprintf(name, PATH_MAX, "perf-pmu-test/format/%s", format->name) < 0) {
+                       pr_err("Failure to set up path for \"%s\"\n", format->name);
+                       goto err_out;
+               }
+               file = openat(dirfd, name, O_WRONLY | O_CREAT, 0600);
+               if (!file) {
+                       pr_err("Failed to open for writing file \"%s\"\n", name);
+                       goto err_out;
+               }
+
+               if (write(file, format->value, strlen(format->value)) < 0) {
+                       pr_err("Failed to write to file \"%s\"\n", name);
+                       close(file);
+                       goto err_out;
+               }
+               close(file);
+       }
+
+       /* Make the PMU reading the files created above. */
+       pmu = perf_pmus__add_test_pmu(dirfd, "perf-pmu-test");
+       if (!pmu)
+               pr_err("Test PMU creation failed\n");
+
+err_out:
+       if (!pmu)
+               test_pmu_put(dir, pmu);
+       if (dirfd >= 0)
+               close(dirfd);
+       return pmu;
 }
 
-static int test__pmu(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
+static int test__pmu_format(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
 {
        char dir[PATH_MAX];
-       char *format;
-       struct parse_events_terms terms;
        struct perf_event_attr attr;
-       struct perf_pmu *pmu;
-       int fd;
-       int ret;
+       struct parse_events_terms terms;
+       int ret = TEST_FAIL;
+       struct perf_pmu *pmu = test_pmu_get(dir, sizeof(dir));
 
-       parse_events_terms__init(&terms);
-       add_test_terms(&terms);
-       pmu = zalloc(sizeof(*pmu));
-       if (!pmu) {
-               parse_events_terms__exit(&terms);
-               return -ENOMEM;
-       }
+       if (!pmu)
+               return TEST_FAIL;
 
-       INIT_LIST_HEAD(&pmu->format);
-       INIT_LIST_HEAD(&pmu->aliases);
-       INIT_LIST_HEAD(&pmu->caps);
-       format = test_format_dir_get(dir, sizeof(dir));
-       if (!format) {
-               free(pmu);
-               parse_events_terms__exit(&terms);
-               return -EINVAL;
+       parse_events_terms__init(&terms);
+       if (parse_events_terms(&terms,
+                               "krava01=15,krava02=170,krava03=1,krava11=27,krava12=1,"
+                               "krava13=2,krava21=119,krava22=11,krava23=2",
+                               NULL)) {
+               pr_err("Term parsing failed\n");
+               goto err_out;
        }
 
        memset(&attr, 0, sizeof(attr));
-
-       fd = open(format, O_DIRECTORY);
-       if (fd < 0) {
-               ret = fd;
-               goto out;
+       ret = perf_pmu__config_terms(pmu, &attr, &terms, /*zero=*/false, /*err=*/NULL);
+       if (ret) {
+               pr_err("perf_pmu__config_terms failed");
+               goto err_out;
        }
 
-       pmu->name = strdup("perf-pmu-test");
-       ret = perf_pmu__format_parse(pmu, fd, /*eager_load=*/true);
-       if (ret)
-               goto out;
+       if (attr.config  != 0xc00000000002a823) {
+               pr_err("Unexpected config value %llx\n", attr.config);
+               goto err_out;
+       }
+       if (attr.config1 != 0x8000400000000145) {
+               pr_err("Unexpected config1 value %llx\n", attr.config1);
+               goto err_out;
+       }
+       if (attr.config2 != 0x0400000020041d07) {
+               pr_err("Unexpected config2 value %llx\n", attr.config2);
+               goto err_out;
+       }
 
-       ret = perf_pmu__config_terms(pmu, &attr, &terms, /*zero=*/false, /*err=*/NULL);
-       if (ret)
-               goto out;
-
-       ret = -EINVAL;
-       if (attr.config  != 0xc00000000002a823)
-               goto out;
-       if (attr.config1 != 0x8000400000000145)
-               goto out;
-       if (attr.config2 != 0x0400000020041d07)
-               goto out;
-
-       ret = 0;
-out:
-       test_format_dir_put(format);
-       perf_pmu__delete(pmu);
+       ret = TEST_OK;
+err_out:
        parse_events_terms__exit(&terms);
+       test_pmu_put(dir, pmu);
        return ret;
 }
 
-DEFINE_SUITE("Parse perf pmu format", pmu);
+static struct test_case tests__pmu[] = {
+       TEST_CASE("Parsing with PMU format directory", pmu_format),
+       {       .name = NULL, }
+};
+
+struct test_suite suite__pmu = {
+       .desc = "Sysfs PMU tests",
+       .test_cases = tests__pmu,
+};
index 0f308b4db2b972f635e3c103613a02e1f3931881..2b9ede311c3182cb88b9f7aede7df920f134a389 100644 (file)
@@ -2585,7 +2585,7 @@ int parse_events_term__term(struct parse_events_term **term,
 }
 
 int parse_events_term__clone(struct parse_events_term **new,
-                            struct parse_events_term *term)
+                            const struct parse_events_term *term)
 {
        char *str;
        struct parse_events_term temp = *term;
index 5695308efab981f601afb4ec019e9041da382365..e7ac1f13376df5efb5a2e2b19ad5f94eaa8afb77 100644 (file)
@@ -178,7 +178,7 @@ int parse_events_term__term(struct parse_events_term **term,
                            enum parse_events__term_type term_rhs,
                            void *loc_term, void *loc_val);
 int parse_events_term__clone(struct parse_events_term **new,
-                            struct parse_events_term *term);
+                            const struct parse_events_term *term);
 void parse_events_term__delete(struct parse_events_term *term);
 
 void parse_events_terms__delete(struct parse_events_terms *terms);
index 74dd5bd49d9a861004069dce524ae75ef2135ec5..fbbc535ed93f1b1b177cb9c99d489a1738c15cd1 100644 (file)
@@ -182,7 +182,7 @@ static void perf_pmu_format__load(const struct perf_pmu *pmu, struct perf_pmu_fo
  * Parse & process all the sysfs attributes located under
  * the directory specified in 'dir' parameter.
  */
-int perf_pmu__format_parse(struct perf_pmu *pmu, int dirfd, bool eager_load)
+static int perf_pmu__format_parse(struct perf_pmu *pmu, int dirfd, bool eager_load)
 {
        struct dirent *evt_ent;
        DIR *format_dir;
@@ -232,7 +232,7 @@ int perf_pmu__format_parse(struct perf_pmu *pmu, int dirfd, bool eager_load)
  * located at:
  * /sys/bus/event_source/devices/<dev>/format as sysfs group attributes.
  */
-static int pmu_format(struct perf_pmu *pmu, int dirfd, const char *name)
+static int pmu_format(struct perf_pmu *pmu, int dirfd, const char *name, bool eager_load)
 {
        int fd;
 
@@ -241,7 +241,7 @@ static int pmu_format(struct perf_pmu *pmu, int dirfd, const char *name)
                return 0;
 
        /* it'll close the fd */
-       if (perf_pmu__format_parse(pmu, fd, /*eager_load=*/false))
+       if (perf_pmu__format_parse(pmu, fd, eager_load))
                return -1;
 
        return 0;
@@ -994,7 +994,8 @@ perf_pmu__arch_init(struct perf_pmu *pmu)
                pmu->mem_events = perf_mem_events;
 }
 
-struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char *name)
+struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char *name,
+                                 bool eager_load)
 {
        struct perf_pmu *pmu;
        __u32 type;
@@ -1023,7 +1024,7 @@ struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char
         * type value and format definitions. Load both right
         * now.
         */
-       if (pmu_format(pmu, dirfd, name))
+       if (pmu_format(pmu, dirfd, name, eager_load))
                goto err;
 
        pmu->is_core = is_pmu_core(name);
index 93d03bd3ecbeb2ce502aa6cdb84acc99e4e826ae..561716aa2b25675a6efc7532a0ab1fd2085219c6 100644 (file)
@@ -214,7 +214,6 @@ int perf_pmu__check_alias(struct perf_pmu *pmu, struct parse_events_terms *head_
                          struct parse_events_error *err);
 int perf_pmu__find_event(struct perf_pmu *pmu, const char *event, void *state, pmu_event_callback cb);
 
-int perf_pmu__format_parse(struct perf_pmu *pmu, int dirfd, bool eager_load);
 void perf_pmu_format__set_value(void *format, int config, unsigned long *bits);
 bool perf_pmu__has_format(const struct perf_pmu *pmu, const char *name);
 int perf_pmu__for_each_format(struct perf_pmu *pmu, void *state, pmu_format_callback cb);
@@ -272,7 +271,8 @@ int perf_pmu__pathname_scnprintf(char *buf, size_t size,
 int perf_pmu__event_source_devices_fd(void);
 int perf_pmu__pathname_fd(int dirfd, const char *pmu_name, const char *filename, int flags);
 
-struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char *lookup_name);
+struct perf_pmu *perf_pmu__lookup(struct list_head *pmus, int dirfd, const char *lookup_name,
+                                 bool eager_load);
 struct perf_pmu *perf_pmu__create_placeholder_core_pmu(struct list_head *core_pmus);
 void perf_pmu__delete(struct perf_pmu *pmu);
 struct perf_pmu *perf_pmus__find_core_pmu(void);
index 2fd369e45832b6335ebd2ef75b5f2e12b41dfc80..b9b4c5eb50027a7a2e92f917f3b373e5bc802932 100644 (file)
@@ -124,7 +124,8 @@ struct perf_pmu *perf_pmus__find(const char *name)
                return NULL;
 
        dirfd = perf_pmu__event_source_devices_fd();
-       pmu = perf_pmu__lookup(core_pmu ? &core_pmus : &other_pmus, dirfd, name);
+       pmu = perf_pmu__lookup(core_pmu ? &core_pmus : &other_pmus, dirfd, name,
+                              /*eager_load=*/false);
        close(dirfd);
 
        if (!pmu) {
@@ -159,7 +160,8 @@ static struct perf_pmu *perf_pmu__find2(int dirfd, const char *name)
        if (core_pmu && read_sysfs_core_pmus)
                return NULL;
 
-       return perf_pmu__lookup(core_pmu ? &core_pmus : &other_pmus, dirfd, name);
+       return perf_pmu__lookup(core_pmu ? &core_pmus : &other_pmus, dirfd, name,
+                               /*eager_load=*/false);
 }
 
 static int pmus_cmp(void *priv __maybe_unused,
@@ -696,3 +698,13 @@ struct perf_pmu *perf_pmus__find_core_pmu(void)
 {
        return perf_pmus__scan_core(NULL);
 }
+
+struct perf_pmu *perf_pmus__add_test_pmu(int test_sysfs_dirfd, const char *name)
+{
+       /*
+        * Some PMU functions read from the sysfs mount point, so care is
+        * needed, hence passing the eager_load flag to load things like the
+        * format files.
+        */
+       return perf_pmu__lookup(&other_pmus, test_sysfs_dirfd, name, /*eager_load=*/true);
+}
index eec599d8aebda65a166a90f6244a1792c9090c36..9d4ded80b8e982204cc640cdf20c5259e289d322 100644 (file)
@@ -24,4 +24,6 @@ int perf_pmus__num_core_pmus(void);
 bool perf_pmus__supports_extended_type(void);
 char *perf_pmus__default_pmu_name(void);
 
+struct perf_pmu *perf_pmus__add_test_pmu(int test_sysfs_dirfd, const char *name);
+
 #endif /* __PMUS_H */