xref: /linux/tools/perf/tests/hwmon_pmu.c (revision 7f71507851fc7764b36a3221839607d3a45c2025)
1 // SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause)
2 #include "debug.h"
3 #include "evlist.h"
4 #include "hwmon_pmu.h"
5 #include "parse-events.h"
6 #include "tests.h"
7 #include <fcntl.h>
8 #include <sys/stat.h>
9 #include <linux/compiler.h>
10 #include <linux/kernel.h>
11 #include <linux/string.h>
12 
13 static const struct test_event {
14 	const char *name;
15 	const char *alias;
16 	long config;
17 } test_events[] = {
18 	{
19 		"temp_test_hwmon_event1",
20 		"temp1",
21 		0xA0001,
22 	},
23 	{
24 		"temp_test_hwmon_event2",
25 		"temp2",
26 		0xA0002,
27 	},
28 };
29 
30 /* Cleanup test PMU directory. */
31 static int test_pmu_put(const char *dir, struct perf_pmu *hwm)
32 {
33 	char buf[PATH_MAX + 20];
34 	int ret;
35 
36 	if (scnprintf(buf, sizeof(buf), "rm -fr %s", dir) < 0) {
37 		pr_err("Failure to set up buffer for \"%s\"\n", dir);
38 		return -EINVAL;
39 	}
40 	ret = system(buf);
41 	if (ret)
42 		pr_err("Failure to \"%s\"\n", buf);
43 
44 	list_del(&hwm->list);
45 	perf_pmu__delete(hwm);
46 	return ret;
47 }
48 
49 /*
50  * Prepare test PMU directory data, normally exported by kernel at
51  * /sys/class/hwmon/hwmon<number>/. Give as input a buffer to hold the file
52  * path, the result is PMU loaded using that directory.
53  */
54 static struct perf_pmu *test_pmu_get(char *dir, size_t sz)
55 {
56 	const char *test_hwmon_name_nl = "A test hwmon PMU\n";
57 	const char *test_hwmon_name = "A test hwmon PMU";
58 	/* Simulated hwmon items. */
59 	const struct test_item {
60 		const char *name;
61 		const char *value;
62 	} test_items[] = {
63 		{ "temp1_label", "test hwmon event1\n", },
64 		{ "temp1_input", "40000\n", },
65 		{ "temp2_label", "test hwmon event2\n", },
66 		{ "temp2_input", "50000\n", },
67 	};
68 	int dirfd, file;
69 	struct perf_pmu *hwm = NULL;
70 	ssize_t len;
71 
72 	/* Create equivalent of sysfs mount point. */
73 	scnprintf(dir, sz, "/tmp/perf-hwmon-pmu-test-XXXXXX");
74 	if (!mkdtemp(dir)) {
75 		pr_err("mkdtemp failed\n");
76 		dir[0] = '\0';
77 		return NULL;
78 	}
79 	dirfd = open(dir, O_DIRECTORY);
80 	if (dirfd < 0) {
81 		pr_err("Failed to open test directory \"%s\"\n", dir);
82 		goto err_out;
83 	}
84 
85 	/* Create the test hwmon directory and give it a name. */
86 	if (mkdirat(dirfd, "hwmon1234", 0755) < 0) {
87 		pr_err("Failed to mkdir hwmon directory\n");
88 		goto err_out;
89 	}
90 	file = openat(dirfd, "hwmon1234/name", O_WRONLY | O_CREAT, 0600);
91 	if (!file) {
92 		pr_err("Failed to open for writing file \"name\"\n");
93 		goto err_out;
94 	}
95 	len = strlen(test_hwmon_name_nl);
96 	if (write(file, test_hwmon_name_nl, len) < len) {
97 		close(file);
98 		pr_err("Failed to write to 'name' file\n");
99 		goto err_out;
100 	}
101 	close(file);
102 
103 	/* Create test hwmon files. */
104 	for (size_t i = 0; i < ARRAY_SIZE(test_items); i++) {
105 		const struct test_item *item = &test_items[i];
106 
107 		file = openat(dirfd, item->name, O_WRONLY | O_CREAT, 0600);
108 		if (!file) {
109 			pr_err("Failed to open for writing file \"%s\"\n", item->name);
110 			goto err_out;
111 		}
112 
113 		if (write(file, item->value, strlen(item->value)) < 0) {
114 			pr_err("Failed to write to file \"%s\"\n", item->name);
115 			close(file);
116 			goto err_out;
117 		}
118 		close(file);
119 	}
120 
121 	/* Make the PMU reading the files created above. */
122 	hwm = perf_pmus__add_test_hwmon_pmu(dirfd, "hwmon1234", test_hwmon_name);
123 	if (!hwm)
124 		pr_err("Test hwmon creation failed\n");
125 
126 err_out:
127 	if (!hwm) {
128 		test_pmu_put(dir, hwm);
129 		if (dirfd >= 0)
130 			close(dirfd);
131 	}
132 	return hwm;
133 }
134 
135 static int do_test(size_t i, bool with_pmu, bool with_alias)
136 {
137 	const char *test_event = with_alias ? test_events[i].alias : test_events[i].name;
138 	struct evlist *evlist = evlist__new();
139 	struct evsel *evsel;
140 	struct parse_events_error err;
141 	int ret;
142 	char str[128];
143 	bool found = false;
144 
145 	if (!evlist) {
146 		pr_err("evlist allocation failed\n");
147 		return TEST_FAIL;
148 	}
149 
150 	if (with_pmu)
151 		snprintf(str, sizeof(str), "hwmon_a_test_hwmon_pmu/%s/", test_event);
152 	else
153 		strlcpy(str, test_event, sizeof(str));
154 
155 	pr_debug("Testing '%s'\n", str);
156 	parse_events_error__init(&err);
157 	ret = parse_events(evlist, str, &err);
158 	if (ret) {
159 		pr_debug("FAILED %s:%d failed to parse event '%s', err %d\n",
160 			 __FILE__, __LINE__, str, ret);
161 		parse_events_error__print(&err, str);
162 		ret = TEST_FAIL;
163 		goto out;
164 	}
165 
166 	ret = TEST_OK;
167 	if (with_pmu ? (evlist->core.nr_entries != 1) : (evlist->core.nr_entries < 1)) {
168 		pr_debug("FAILED %s:%d Unexpected number of events for '%s' of %d\n",
169 			 __FILE__, __LINE__, str, evlist->core.nr_entries);
170 		ret = TEST_FAIL;
171 		goto out;
172 	}
173 
174 	evlist__for_each_entry(evlist, evsel) {
175 		if (!evsel->pmu || !evsel->pmu->name ||
176 		    strcmp(evsel->pmu->name, "hwmon_a_test_hwmon_pmu"))
177 			continue;
178 
179 		if (evsel->core.attr.config != (u64)test_events[i].config) {
180 			pr_debug("FAILED %s:%d Unexpected config for '%s', %lld != %ld\n",
181 				__FILE__, __LINE__, str,
182 				evsel->core.attr.config,
183 				test_events[i].config);
184 			ret = TEST_FAIL;
185 			goto out;
186 		}
187 		found = true;
188 	}
189 
190 	if (!found) {
191 		pr_debug("FAILED %s:%d Didn't find hwmon event '%s' in parsed evsels\n",
192 			 __FILE__, __LINE__, str);
193 		ret = TEST_FAIL;
194 	}
195 
196 out:
197 	parse_events_error__exit(&err);
198 	evlist__delete(evlist);
199 	return ret;
200 }
201 
202 static int test__hwmon_pmu(bool with_pmu)
203 {
204 	char dir[PATH_MAX];
205 	struct perf_pmu *pmu = test_pmu_get(dir, sizeof(dir));
206 	int ret = TEST_OK;
207 
208 	if (!pmu)
209 		return TEST_FAIL;
210 
211 	for (size_t i = 0; i < ARRAY_SIZE(test_events); i++) {
212 		ret = do_test(i, with_pmu, /*with_alias=*/false);
213 
214 		if (ret != TEST_OK)
215 			break;
216 
217 		ret = do_test(i, with_pmu, /*with_alias=*/true);
218 
219 		if (ret != TEST_OK)
220 			break;
221 	}
222 	test_pmu_put(dir, pmu);
223 	return ret;
224 }
225 
226 static int test__hwmon_pmu_without_pmu(struct test_suite *test __maybe_unused,
227 				      int subtest __maybe_unused)
228 {
229 	return test__hwmon_pmu(/*with_pmu=*/false);
230 }
231 
232 static int test__hwmon_pmu_with_pmu(struct test_suite *test __maybe_unused,
233 				   int subtest __maybe_unused)
234 {
235 	return test__hwmon_pmu(/*with_pmu=*/true);
236 }
237 
238 static int test__parse_hwmon_filename(struct test_suite *test __maybe_unused,
239 				      int subtest __maybe_unused)
240 {
241 	const struct hwmon_parse_test {
242 		const char *filename;
243 		enum hwmon_type type;
244 		int number;
245 		enum hwmon_item item;
246 		bool alarm;
247 		bool parse_ok;
248 	} tests[] = {
249 		{
250 			.filename = "cpu0_accuracy",
251 			.type = HWMON_TYPE_CPU,
252 			.number = 0,
253 			.item = HWMON_ITEM_ACCURACY,
254 			.alarm = false,
255 			.parse_ok = true,
256 		},
257 		{
258 			.filename = "temp1_input",
259 			.type = HWMON_TYPE_TEMP,
260 			.number = 1,
261 			.item = HWMON_ITEM_INPUT,
262 			.alarm = false,
263 			.parse_ok = true,
264 		},
265 		{
266 			.filename = "fan2_vid",
267 			.type = HWMON_TYPE_FAN,
268 			.number = 2,
269 			.item = HWMON_ITEM_VID,
270 			.alarm = false,
271 			.parse_ok = true,
272 		},
273 		{
274 			.filename = "power3_crit_alarm",
275 			.type = HWMON_TYPE_POWER,
276 			.number = 3,
277 			.item = HWMON_ITEM_CRIT,
278 			.alarm = true,
279 			.parse_ok = true,
280 		},
281 		{
282 			.filename = "intrusion4_average_interval_min_alarm",
283 			.type = HWMON_TYPE_INTRUSION,
284 			.number = 4,
285 			.item = HWMON_ITEM_AVERAGE_INTERVAL_MIN,
286 			.alarm = true,
287 			.parse_ok = true,
288 		},
289 		{
290 			.filename = "badtype5_baditem",
291 			.type = HWMON_TYPE_NONE,
292 			.number = 5,
293 			.item = HWMON_ITEM_NONE,
294 			.alarm = false,
295 			.parse_ok = false,
296 		},
297 		{
298 			.filename = "humidity6_baditem",
299 			.type = HWMON_TYPE_NONE,
300 			.number = 6,
301 			.item = HWMON_ITEM_NONE,
302 			.alarm = false,
303 			.parse_ok = false,
304 		},
305 	};
306 
307 	for (size_t i = 0; i < ARRAY_SIZE(tests); i++) {
308 		enum hwmon_type type;
309 		int number;
310 		enum hwmon_item item;
311 		bool alarm;
312 
313 		TEST_ASSERT_EQUAL("parse_hwmon_filename",
314 				parse_hwmon_filename(
315 					tests[i].filename,
316 					&type,
317 					&number,
318 					&item,
319 					&alarm),
320 				tests[i].parse_ok
321 			);
322 		if (tests[i].parse_ok) {
323 			TEST_ASSERT_EQUAL("parse_hwmon_filename type", type, tests[i].type);
324 			TEST_ASSERT_EQUAL("parse_hwmon_filename number", number, tests[i].number);
325 			TEST_ASSERT_EQUAL("parse_hwmon_filename item", item, tests[i].item);
326 			TEST_ASSERT_EQUAL("parse_hwmon_filename alarm", alarm, tests[i].alarm);
327 		}
328 	}
329 	return TEST_OK;
330 }
331 
332 static struct test_case tests__hwmon_pmu[] = {
333 	TEST_CASE("Basic parsing test", parse_hwmon_filename),
334 	TEST_CASE("Parsing without PMU name", hwmon_pmu_without_pmu),
335 	TEST_CASE("Parsing with PMU name", hwmon_pmu_with_pmu),
336 	{	.name = NULL, }
337 };
338 
339 struct test_suite suite__hwmon_pmu = {
340 	.desc = "Hwmon PMU",
341 	.test_cases = tests__hwmon_pmu,
342 };
343