summaryrefslogtreecommitdiffstats
path: root/tools/perf/tests/pmu.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/perf/tests/pmu.c')
-rw-r--r--tools/perf/tests/pmu.c467
1 files changed, 308 insertions, 159 deletions
diff --git a/tools/perf/tests/pmu.c b/tools/perf/tests/pmu.c
index 8f18127d87..06cc0e46cb 100644
--- a/tools/perf/tests/pmu.c
+++ b/tools/perf/tests/pmu.c
@@ -1,204 +1,353 @@
// SPDX-License-Identifier: GPL-2.0
+#include "evlist.h"
+#include "evsel.h"
#include "parse-events.h"
#include "pmu.h"
#include "tests.h"
+#include "debug.h"
+#include "fncache.h"
+#include <api/fs/fs.h>
+#include <ctype.h>
+#include <dirent.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", },
-};
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
-/* 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,
- },
-};
+/* Fake PMUs created in temp directory. */
+static LIST_HEAD(test_pmus);
+
+/* Cleanup test PMU directory. */
+static int test_pmu_put(const char *dir, struct perf_pmu *pmu)
+{
+ char buf[PATH_MAX + 20];
+ int ret;
+
+ 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);
+
+ list_del(&pmu->list);
+ perf_pmu__delete(pmu);
+ return ret;
+}
/*
- * Prepare format directory data, exported by kernel
- * at /sys/bus/event_source/devices/<dev>/format.
+ * 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 char *test_format_dir_get(char *dir, size_t sz)
+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", },
+ };
+ const char *test_event = "krava01=15,krava02=170,krava03=1,krava11=27,krava12=1,"
+ "krava13=2,krava21=119,krava22=11,krava23=2\n";
+
+ char name[PATH_MAX];
+ int dirfd, file;
+ struct perf_pmu *pmu = NULL;
+ ssize_t len;
- snprintf(dir, sz, "/tmp/perf-pmu-test-format-XXXXXX");
- if (!mkdtemp(dir))
+ /* 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_formats); i++) {
- char name[PATH_MAX];
- struct test_format *format = &test_formats[i];
- FILE *file;
+ /* 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);
- scnprintf(name, PATH_MAX, "%s/%s", dir, format->name);
+ /* 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];
- file = fopen(name, "w");
- if (!file)
- return NULL;
+ 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 (1 != fwrite(format->value, strlen(format->value), 1, file))
- break;
+ 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);
+ }
- fclose(file);
+ /* Create test event. */
+ if (mkdirat(dirfd, "perf-pmu-test/events", 0755) < 0) {
+ pr_err("Failed to mkdir PMU events directory\n");
+ goto err_out;
+ }
+ file = openat(dirfd, "perf-pmu-test/events/test-event", O_WRONLY | O_CREAT, 0600);
+ if (!file) {
+ pr_err("Failed to open for writing file \"type\"\n");
+ goto err_out;
+ }
+ len = strlen(test_event);
+ if (write(file, test_event, len) < len) {
+ close(file);
+ pr_err("Failed to write to 'test-event' file\n");
+ goto err_out;
}
+ close(file);
- return dir;
+ /* 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;
}
-/* Cleanup format directory. */
-static int test_format_dir_put(char *dir)
+static int test__pmu_format(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
- char buf[PATH_MAX + 20];
+ char dir[PATH_MAX];
+ struct perf_event_attr attr;
+ struct parse_events_terms terms;
+ int ret = TEST_FAIL;
+ struct perf_pmu *pmu = test_pmu_get(dir, sizeof(dir));
- snprintf(buf, sizeof(buf), "rm -f %s/*\n", dir);
- if (system(buf))
- return -1;
+ if (!pmu)
+ return TEST_FAIL;
- snprintf(buf, sizeof(buf), "rmdir %s\n", dir);
- return system(buf);
+ 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));
+ ret = perf_pmu__config_terms(pmu, &attr, &terms, /*zero=*/false, /*err=*/NULL);
+ if (ret) {
+ pr_err("perf_pmu__config_terms failed");
+ goto err_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 = TEST_OK;
+err_out:
+ parse_events_terms__exit(&terms);
+ test_pmu_put(dir, pmu);
+ return ret;
}
-static void add_test_terms(struct parse_events_terms *terms)
+static int test__pmu_events(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
{
- unsigned int i;
+ char dir[PATH_MAX];
+ struct parse_events_error err;
+ struct evlist *evlist;
+ struct evsel *evsel;
+ struct perf_event_attr *attr;
+ int ret = TEST_FAIL;
+ struct perf_pmu *pmu = test_pmu_get(dir, sizeof(dir));
+ const char *event = "perf-pmu-test/test-event/";
- for (i = 0; i < ARRAY_SIZE(test_terms); i++) {
- struct parse_events_term *clone;
- parse_events_term__clone(&clone, &test_terms[i]);
- list_add_tail(&clone->list, &terms->terms);
+ if (!pmu)
+ return TEST_FAIL;
+
+ evlist = evlist__new();
+ if (evlist == NULL) {
+ pr_err("Failed allocation");
+ goto err_out;
+ }
+ parse_events_error__init(&err);
+ ret = parse_events(evlist, event, &err);
+ if (ret) {
+ pr_debug("failed to parse event '%s', err %d\n", event, ret);
+ parse_events_error__print(&err, event);
+ if (parse_events_error__contains(&err, "can't access trace events"))
+ ret = TEST_SKIP;
+ goto err_out;
+ }
+ evsel = evlist__first(evlist);
+ attr = &evsel->core.attr;
+ 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 = TEST_OK;
+err_out:
+ parse_events_error__exit(&err);
+ evlist__delete(evlist);
+ test_pmu_put(dir, pmu);
+ return ret;
}
-static int test__pmu(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
+static bool permitted_event_name(const char *name)
{
- char dir[PATH_MAX];
- char *format;
- struct parse_events_terms terms;
- struct perf_event_attr attr;
- struct perf_pmu *pmu;
- int fd;
- int ret;
+ bool has_lower = false, has_upper = false;
- parse_events_terms__init(&terms);
- add_test_terms(&terms);
- pmu = zalloc(sizeof(*pmu));
- if (!pmu) {
- parse_events_terms__exit(&terms);
- return -ENOMEM;
- }
-
- 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;
+ for (size_t i = 0; i < strlen(name); i++) {
+ char c = name[i];
+
+ if (islower(c)) {
+ if (has_upper)
+ return false;
+ has_lower = true;
+ continue;
+ }
+ if (isupper(c)) {
+ if (has_lower)
+ return false;
+ has_upper = true;
+ continue;
+ }
+ if (!isdigit(c) && c != '.' && c != '_' && c != '-')
+ return false;
}
+ return true;
+}
- memset(&attr, 0, sizeof(attr));
+static int test__pmu_event_names(struct test_suite *test __maybe_unused,
+ int subtest __maybe_unused)
+{
+ char path[PATH_MAX];
+ DIR *pmu_dir, *event_dir;
+ struct dirent *pmu_dent, *event_dent;
+ const char *sysfs = sysfs__mountpoint();
+ int ret = TEST_OK;
- fd = open(format, O_DIRECTORY);
- if (fd < 0) {
- ret = fd;
- goto out;
+ if (!sysfs) {
+ pr_err("Sysfs not mounted\n");
+ return TEST_FAIL;
}
- pmu->name = strdup("perf-pmu-test");
- ret = perf_pmu__format_parse(pmu, fd, /*eager_load=*/true);
- if (ret)
- goto out;
+ snprintf(path, sizeof(path), "%s/bus/event_source/devices/", sysfs);
+ pmu_dir = opendir(path);
+ if (!pmu_dir) {
+ pr_err("Error opening \"%s\"\n", path);
+ return TEST_FAIL;
+ }
+ while ((pmu_dent = readdir(pmu_dir))) {
+ if (!strcmp(pmu_dent->d_name, ".") ||
+ !strcmp(pmu_dent->d_name, ".."))
+ continue;
- 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);
- parse_events_terms__exit(&terms);
+ snprintf(path, sizeof(path), "%s/bus/event_source/devices/%s/type",
+ sysfs, pmu_dent->d_name);
+
+ /* Does it look like a PMU? */
+ if (!file_available(path))
+ continue;
+
+ /* Process events. */
+ snprintf(path, sizeof(path), "%s/bus/event_source/devices/%s/events",
+ sysfs, pmu_dent->d_name);
+
+ event_dir = opendir(path);
+ if (!event_dir) {
+ pr_debug("Skipping as no event directory \"%s\"\n", path);
+ continue;
+ }
+ while ((event_dent = readdir(event_dir))) {
+ const char *event_name = event_dent->d_name;
+
+ if (!strcmp(event_name, ".") || !strcmp(event_name, ".."))
+ continue;
+
+ if (!permitted_event_name(event_name)) {
+ pr_err("Invalid sysfs event name: %s/%s\n",
+ pmu_dent->d_name, event_name);
+ ret = TEST_FAIL;
+ }
+ }
+ closedir(event_dir);
+ }
+ closedir(pmu_dir);
return ret;
}
-DEFINE_SUITE("Parse perf pmu format", pmu);
+static struct test_case tests__pmu[] = {
+ TEST_CASE("Parsing with PMU format directory", pmu_format),
+ TEST_CASE("Parsing with PMU event", pmu_events),
+ TEST_CASE("PMU event names", pmu_event_names),
+ { .name = NULL, }
+};
+
+struct test_suite suite__pmu = {
+ .desc = "Sysfs PMU tests",
+ .test_cases = tests__pmu,
+};