1 /******************************************************************************/
2 /* */
3 /* Paul Mackerras <paulus@samba.org>, 2009 */
4 /* */
5 /* This program is free software; you can redistribute it and/or modify */
6 /* it under the terms of the GNU General Public License as published by */
7 /* the Free Software Foundation; either version 2 of the License, or */
8 /* (at your option) any later version. */
9 /* */
10 /* This program is distributed in the hope that it will be useful, */
11 /* but WITHOUT ANY WARRANTY; without even the implied warranty of */
12 /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See */
13 /* the GNU General Public License for more details. */
14 /* */
15 /* You should have received a copy of the GNU General Public License */
16 /* along with this program; if not, write to the Free Software */
17 /* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
18 /* */
19 /******************************************************************************/
20 /*
21 Here's a little test program that checks whether software counters
22 (specifically, the task clock counter) work correctly when they're in
23 a group with hardware counters.
24
25 What it does is to create several groups, each with one hardware
26 counter, counting instructions, plus a task clock counter. It needs
27 to know an upper bound N on the number of hardware counters you have
28 (N defaults to 8), and it creates N+4 groups to force them to be
29 multiplexed. It also creates an overall task clock counter.
30
31 Then it spins for a while, and then stops all the counters and reads
32 them. It takes the total of the task clock counters in the groups and
33 computes the ratio of that total to the overall execution time from
34 the overall task clock counter.
35
36 That ratio should be equal to the number of actual hardware counters
37 that can count instructions. If the task clock counters in the groups
38 don't stop when their group gets taken off the PMU, the ratio will
39 instead be close to N+4. The program will declare that the test fails
40 if the ratio is greater than N (actually, N + 0.0001 to allow for FP
41 rounding errors).
42
43 Could someone run this on x86 on the latest PCL tree and let me know
44 what happens? I don't have an x86 crash box easily to hand. On
45 powerpc, it passes, but I think that is because I am missing setting
46 counter->prev_count in arch/powerpc/kernel/perf_counter.c, and I think
47 that means that enabling/disabling a group with a task clock counter
48 in it won't work correctly (I'll do a test program for that next).
49
50 Usage is: ./performance_counter02 [-v]
51
52 The -v flag makes it print out the values of each counter.
53 */
54
55 #include <stdio.h>
56 #include <stddef.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <fcntl.h>
60 #include <poll.h>
61 #include <unistd.h>
62 #include <errno.h>
63 #include "config.h"
64 #include <sys/prctl.h>
65 #include <sys/types.h>
66 #include <linux/types.h>
67 #include <sched.h>
68
69 #if HAVE_PERF_EVENT_ATTR
70 # include <linux/perf_event.h>
71 #endif
72
73 #include "test.h"
74 #include "safe_macros.h"
75 #include "lapi/syscalls.h"
76
77 char *TCID = "perf_event_open02";
78 int TST_TOTAL = 1;
79
80 #if HAVE_PERF_EVENT_ATTR
81
82 #define MAX_CTRS 1000
83 #define LOOPS 100000000
84
85 static int count_hardware_counters(void);
86 static void setup(void);
87 static void verify(void);
88 static void cleanup(void);
89 static void help(void);
90
91 static int n, nhw;
92 static int verbose;
93 static option_t options[] = {
94 {"v", &verbose, NULL},
95 {NULL, NULL, NULL},
96 };
97
98 static int tsk0;
99 static int hwfd[MAX_CTRS], tskfd[MAX_CTRS];
100
main(int ac,char ** av)101 int main(int ac, char **av)
102 {
103 int lc;
104
105 tst_parse_opts(ac, av, options, help);
106
107 setup();
108
109 for (lc = 0; TEST_LOOPING(lc); lc++) {
110 tst_count = 0;
111 verify();
112 }
113
114 cleanup();
115 tst_exit();
116 }
117
perf_event_open(struct perf_event_attr * hw_event,pid_t pid,int cpu,int group_fd,unsigned long flags)118 static int perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
119 int cpu, int group_fd, unsigned long flags)
120 {
121 int ret;
122
123 ret = ltp_syscall(__NR_perf_event_open, hw_event, pid, cpu,
124 group_fd, flags);
125 return ret;
126 }
127
128
do_work(void)129 static void do_work(void)
130 {
131 int i;
132
133 for (i = 0; i < LOOPS; ++i)
134 asm volatile (""::"g" (i));
135 }
136
137 struct read_format {
138 unsigned long long value;
139 /* if PERF_FORMAT_TOTAL_TIME_ENABLED */
140 unsigned long long time_enabled;
141 /* if PERF_FORMAT_TOTAL_TIME_RUNNING */
142 unsigned long long time_running;
143 };
144
count_hardware_counters(void)145 static int count_hardware_counters(void)
146 {
147 struct perf_event_attr hw_event;
148 int i, hwctrs = 0;
149 int fdarry[MAX_CTRS];
150 struct read_format buf;
151
152 memset(&hw_event, 0, sizeof(struct perf_event_attr));
153
154 hw_event.type = PERF_TYPE_HARDWARE;
155 hw_event.size = sizeof(struct perf_event_attr);
156 hw_event.disabled = 1;
157 hw_event.config = PERF_COUNT_HW_INSTRUCTIONS;
158 hw_event.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED |
159 PERF_FORMAT_TOTAL_TIME_RUNNING;
160
161 for (i = 0; i < MAX_CTRS; i++) {
162 fdarry[i] = perf_event_open(&hw_event, 0, -1, -1, 0);
163 if (fdarry[i] == -1) {
164 if (errno == ENOENT || errno == ENODEV) {
165 tst_brkm(TCONF | TERRNO, cleanup,
166 "PERF_COUNT_HW_INSTRUCTIONS not supported");
167 }
168 tst_brkm(TBROK | TERRNO, cleanup,
169 "perf_event_open failed at iteration:%d", i);
170 }
171
172 if (prctl(PR_TASK_PERF_EVENTS_ENABLE) == -1) {
173 tst_brkm(TBROK | TERRNO, cleanup,
174 "prctl(PR_TASK_PERF_EVENTS_ENABLE) failed");
175 }
176
177 do_work();
178
179 if (prctl(PR_TASK_PERF_EVENTS_DISABLE) == -1) {
180 tst_brkm(TBROK | TERRNO, cleanup,
181 "prctl(PR_TASK_PERF_EVENTS_DISABLE) failed");
182 }
183
184 if (read(fdarry[i], &buf, sizeof(buf)) != sizeof(buf)) {
185 tst_brkm(TBROK | TERRNO, cleanup,
186 "error reading counter(s)");
187 }
188
189 if (verbose == 1) {
190 printf("at iteration:%d value:%lld time_enabled:%lld "
191 "time_running:%lld\n", i, buf.value,
192 buf.time_enabled, buf.time_running);
193 }
194
195 /*
196 * Normally time_enabled and time_running are the same value.
197 * But if more events are started than available counter slots
198 * on the PMU, then multiplexing happens and events run only
199 * part of the time. Time_enabled and time_running's values
200 * will be different. In this case the time_enabled and time_
201 * running values can be used to scale an estimated value for
202 * the count. So if buf.time_enabled and buf.time_running are
203 * not equal, we can think that PMU hardware counters
204 * multiplexing happens and the number of the opened events
205 * are the number of max available hardware counters.
206 */
207 if (buf.time_enabled != buf.time_running) {
208 hwctrs = i;
209 break;
210 }
211 }
212
213 for (i = 0; i <= hwctrs; i++)
214 SAFE_CLOSE(cleanup, fdarry[i]);
215
216 return hwctrs;
217 }
218
setup(void)219 static void setup(void)
220 {
221 int i;
222 struct perf_event_attr tsk_event, hw_event;
223
224 /*
225 * According to perf_event_open's manpage, the official way of
226 * knowing if perf_event_open() support is enabled is checking for
227 * the existence of the file /proc/sys/kernel/perf_event_paranoid.
228 */
229 if (access("/proc/sys/kernel/perf_event_paranoid", F_OK) == -1)
230 tst_brkm(TCONF, NULL, "Kernel doesn't have perf_event support");
231
232 tst_sig(NOFORK, DEF_HANDLER, cleanup);
233
234 TEST_PAUSE;
235
236 nhw = count_hardware_counters();
237 n = nhw + 4;
238
239 memset(&hw_event, 0, sizeof(struct perf_event_attr));
240 memset(&tsk_event, 0, sizeof(struct perf_event_attr));
241
242 tsk_event.type = PERF_TYPE_SOFTWARE;
243 tsk_event.size = sizeof(struct perf_event_attr);
244 tsk_event.disabled = 1;
245 tsk_event.config = PERF_COUNT_SW_TASK_CLOCK;
246
247 hw_event.type = PERF_TYPE_HARDWARE;
248 hw_event.size = sizeof(struct perf_event_attr);
249 hw_event.disabled = 1;
250 hw_event.config = PERF_COUNT_HW_INSTRUCTIONS;
251
252 tsk0 = perf_event_open(&tsk_event, 0, -1, -1, 0);
253 if (tsk0 == -1) {
254 tst_brkm(TBROK | TERRNO, cleanup, "perf_event_open failed");
255 } else {
256 tsk_event.disabled = 0;
257 for (i = 0; i < n; ++i) {
258 hwfd[i] = perf_event_open(&hw_event, 0, -1, -1, 0);
259 tskfd[i] = perf_event_open(&tsk_event, 0, -1,
260 hwfd[i], 0);
261 if (tskfd[i] == -1 || hwfd[i] == -1) {
262 tst_brkm(TBROK | TERRNO, cleanup,
263 "perf_event_open failed");
264 }
265 }
266 }
267 }
268
cleanup(void)269 static void cleanup(void)
270 {
271 int i;
272
273 for (i = 0; i < n; i++) {
274 if (hwfd[i] > 0 && close(hwfd[i]) == -1)
275 tst_resm(TWARN | TERRNO, "close(%d) failed", hwfd[i]);
276 if (tskfd[i] > 0 && close(tskfd[i]) == -1)
277 tst_resm(TWARN | TERRNO, "close(%d) failed", tskfd[i]);
278 }
279
280 if (tsk0 > 0 && close(tsk0) == -1)
281 tst_resm(TWARN | TERRNO, "close(%d) failed", tsk0);
282 }
283
verify(void)284 static void verify(void)
285 {
286 unsigned long long vt0, vt[MAX_CTRS], vh[MAX_CTRS];
287 unsigned long long vtsum = 0, vhsum = 0;
288 int i;
289 double ratio;
290 struct sched_param sparam = {.sched_priority = 1};
291
292 if (sched_setscheduler(0, SCHED_FIFO, &sparam)) {
293 tst_brkm(TBROK | TERRNO, cleanup,
294 "sched_setscheduler(0, SCHED_FIFO, ...) failed");
295 }
296
297 if (prctl(PR_TASK_PERF_EVENTS_ENABLE) == -1) {
298 tst_brkm(TBROK | TERRNO, cleanup,
299 "prctl(PR_TASK_PERF_EVENTS_ENABLE) failed");
300 }
301
302 do_work();
303
304 if (prctl(PR_TASK_PERF_EVENTS_DISABLE) == -1) {
305 tst_brkm(TBROK | TERRNO, cleanup,
306 "prctl(PR_TASK_PERF_EVENTS_DISABLE) failed");
307 }
308
309 sparam.sched_priority = 0;
310 if (sched_setscheduler(0, SCHED_OTHER, &sparam)) {
311 tst_brkm(TBROK | TERRNO, cleanup,
312 "sched_setscheduler(0, SCHED_OTHER, ...) failed");
313 }
314
315 if (read(tsk0, &vt0, sizeof(vt0)) != sizeof(vt0)) {
316 tst_brkm(TBROK | TERRNO, cleanup,
317 "error reading task clock counter");
318 }
319
320 for (i = 0; i < n; ++i) {
321 if (read(tskfd[i], &vt[i], sizeof(vt[i])) != sizeof(vt[i]) ||
322 read(hwfd[i], &vh[i], sizeof(vh[i])) != sizeof(vh[i])) {
323 tst_brkm(TBROK | TERRNO, cleanup,
324 "error reading counter(s)");
325 }
326 vtsum += vt[i];
327 vhsum += vh[i];
328 }
329
330 tst_resm(TINFO, "overall task clock: %llu", vt0);
331 tst_resm(TINFO, "hw sum: %llu, task clock sum: %llu", vhsum, vtsum);
332
333 if (verbose == 1) {
334 printf("hw counters:");
335 for (i = 0; i < n; ++i)
336 printf(" %llu", vh[i]);
337 printf("\ntask clock counters:");
338 for (i = 0; i < n; ++i)
339 printf(" %llu", vt[i]);
340 printf("\n");
341 }
342
343 ratio = (double)vtsum / vt0;
344 tst_resm(TINFO, "ratio: %lf", ratio);
345 if (ratio > nhw + 0.0001) {
346 tst_resm(TFAIL, "test failed (ratio was greater than )");
347 } else {
348 tst_resm(TPASS, "test passed");
349 }
350 }
351
help(void)352 static void help(void)
353 {
354 printf(" -v Print verbose information\n");
355 }
356
357 #else
358
main(void)359 int main(void)
360 {
361 tst_brkm(TCONF, NULL, "This system doesn't have "
362 "header file:<linux/perf_event.h> or "
363 "no struct perf_event_attr defined");
364 }
365 #endif
366