xref: /linux/tools/perf/tests/hwmon_pmu.c (revision 1f9d9b62cfd54fb06df9b5f2f2868324bee3aab4)
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->core.nr_entries != 1) : (evlist->core.nr_entries < 1)) {
188 		pr_debug("FAILED %s:%d Unexpected number of events for '%s' of %d\n",
189 			 __FILE__, __LINE__, str, evlist->core.nr_entries);
190 		ret = TEST_FAIL;
191 		goto out;
192 	}
193 
194 	evlist__for_each_entry(evlist, evsel) {
195 		if (!evsel->pmu || !evsel->pmu->name ||
196 		    strcmp(evsel->pmu->name, "hwmon_a_test_hwmon_pmu"))
197 			continue;
198 
199 		if (evsel->core.attr.config != (u64)test_events[i].key.type_and_num) {
200 			pr_debug("FAILED %s:%d Unexpected config for '%s', %" PRIu64 " != %ld\n",
201 				__FILE__, __LINE__, str,
202 				(uint64_t)evsel->core.attr.config,
203 				test_events[i].key.type_and_num);
204 			ret = TEST_FAIL;
205 			goto out;
206 		}
207 		found = true;
208 	}
209 
210 	if (!found) {
211 		pr_debug("FAILED %s:%d Didn't find hwmon event '%s' in parsed evsels\n",
212 			 __FILE__, __LINE__, str);
213 		ret = TEST_FAIL;
214 	}
215 
216 out:
217 	parse_events_error__exit(&err);
218 	evlist__delete(evlist);
219 	return ret;
220 }
221 
222 static int test__hwmon_pmu(bool with_pmu)
223 {
224 	char dir[PATH_MAX];
225 	struct perf_pmu *pmu = test_pmu_get(dir, sizeof(dir));
226 	int ret = TEST_OK;
227 
228 	if (!pmu)
229 		return TEST_FAIL;
230 
231 	for (size_t i = 0; i < ARRAY_SIZE(test_events); i++) {
232 		ret = do_test(i, with_pmu, /*with_alias=*/false);
233 
234 		if (ret != TEST_OK)
235 			break;
236 
237 		ret = do_test(i, with_pmu, /*with_alias=*/true);
238 
239 		if (ret != TEST_OK)
240 			break;
241 	}
242 	test_pmu_put(dir, pmu);
243 	return ret;
244 }
245 
246 static int test__hwmon_pmu_without_pmu(struct test_suite *test __maybe_unused,
247 				      int subtest __maybe_unused)
248 {
249 	return test__hwmon_pmu(/*with_pmu=*/false);
250 }
251 
252 static int test__hwmon_pmu_with_pmu(struct test_suite *test __maybe_unused,
253 				   int subtest __maybe_unused)
254 {
255 	return test__hwmon_pmu(/*with_pmu=*/true);
256 }
257 
258 static int test__parse_hwmon_filename(struct test_suite *test __maybe_unused,
259 				      int subtest __maybe_unused)
260 {
261 	const struct hwmon_parse_test {
262 		const char *filename;
263 		enum hwmon_type type;
264 		int number;
265 		enum hwmon_item item;
266 		bool alarm;
267 		bool parse_ok;
268 	} tests[] = {
269 		{
270 			.filename = "cpu0_accuracy",
271 			.type = HWMON_TYPE_CPU,
272 			.number = 0,
273 			.item = HWMON_ITEM_ACCURACY,
274 			.alarm = false,
275 			.parse_ok = true,
276 		},
277 		{
278 			.filename = "temp1_input",
279 			.type = HWMON_TYPE_TEMP,
280 			.number = 1,
281 			.item = HWMON_ITEM_INPUT,
282 			.alarm = false,
283 			.parse_ok = true,
284 		},
285 		{
286 			.filename = "fan2_vid",
287 			.type = HWMON_TYPE_FAN,
288 			.number = 2,
289 			.item = HWMON_ITEM_VID,
290 			.alarm = false,
291 			.parse_ok = true,
292 		},
293 		{
294 			.filename = "power3_crit_alarm",
295 			.type = HWMON_TYPE_POWER,
296 			.number = 3,
297 			.item = HWMON_ITEM_CRIT,
298 			.alarm = true,
299 			.parse_ok = true,
300 		},
301 		{
302 			.filename = "intrusion4_average_interval_min_alarm",
303 			.type = HWMON_TYPE_INTRUSION,
304 			.number = 4,
305 			.item = HWMON_ITEM_AVERAGE_INTERVAL_MIN,
306 			.alarm = true,
307 			.parse_ok = true,
308 		},
309 		{
310 			.filename = "badtype5_baditem",
311 			.type = HWMON_TYPE_NONE,
312 			.number = 5,
313 			.item = HWMON_ITEM_NONE,
314 			.alarm = false,
315 			.parse_ok = false,
316 		},
317 		{
318 			.filename = "humidity6_baditem",
319 			.type = HWMON_TYPE_NONE,
320 			.number = 6,
321 			.item = HWMON_ITEM_NONE,
322 			.alarm = false,
323 			.parse_ok = false,
324 		},
325 	};
326 
327 	for (size_t i = 0; i < ARRAY_SIZE(tests); i++) {
328 		enum hwmon_type type;
329 		int number;
330 		enum hwmon_item item;
331 		bool alarm;
332 
333 		TEST_ASSERT_EQUAL("parse_hwmon_filename",
334 				parse_hwmon_filename(
335 					tests[i].filename,
336 					&type,
337 					&number,
338 					&item,
339 					&alarm),
340 				tests[i].parse_ok
341 			);
342 		if (tests[i].parse_ok) {
343 			TEST_ASSERT_EQUAL("parse_hwmon_filename type", type, tests[i].type);
344 			TEST_ASSERT_EQUAL("parse_hwmon_filename number", number, tests[i].number);
345 			TEST_ASSERT_EQUAL("parse_hwmon_filename item", item, tests[i].item);
346 			TEST_ASSERT_EQUAL("parse_hwmon_filename alarm", alarm, tests[i].alarm);
347 		}
348 	}
349 	return TEST_OK;
350 }
351 
352 static struct test_case tests__hwmon_pmu[] = {
353 	TEST_CASE("Basic parsing test", parse_hwmon_filename),
354 	TEST_CASE("Parsing without PMU name", hwmon_pmu_without_pmu),
355 	TEST_CASE("Parsing with PMU name", hwmon_pmu_with_pmu),
356 	{	.name = NULL, }
357 };
358 
359 struct test_suite suite__hwmon_pmu = {
360 	.desc = "Hwmon PMU",
361 	.test_cases = tests__hwmon_pmu,
362 };
363