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