diff options
author | Peter Zijlstra <a.p.zijlstra@chello.nl> | 2009-04-08 09:01:31 -0400 |
---|---|---|
committer | Ingo Molnar <mingo@elte.hu> | 2009-04-08 13:05:54 -0400 |
commit | de9ac07bbf8f51e0ce40e5428c3a8f627bd237c2 (patch) | |
tree | 8aa950c28ce7186d9e1ebe86332ffac6c952c00a | |
parent | 8d1b2d9361b494bfc761700c348c65ebbe3deb5b (diff) |
perf_counter: some simple userspace profiling
# perf-record make -j4 kernel/
# perf-report | tail -15
0.39 cc1 [kernel] lock_acquired
0.42 cc1 [kernel] lock_acquire
0.51 cc1 [ user ] /lib64/libc-2.8.90.so: _int_free
0.51 as [kernel] clear_page_c
0.53 cc1 [ user ] /lib64/libc-2.8.90.so: memcpy
0.56 cc1 [ user ] /lib64/libc-2.8.90.so: _IO_vfprintf
0.63 cc1 [kernel] lock_release
0.67 cc1 [ user ] /lib64/libc-2.8.90.so: strlen
0.68 cc1 [kernel] debug_smp_processor_id
1.38 cc1 [ user ] /lib64/libc-2.8.90.so: _int_malloc
1.55 cc1 [ user ] /lib64/libc-2.8.90.so: memset
1.77 cc1 [kernel] __lock_acquire
1.88 cc1 [kernel] clear_page_c
3.61 as [ user ] /usr/bin/as: <unknown>
59.16 cc1 [ user ] /usr/libexec/gcc/x86_64-redhat-linux/4.3.2/cc1: <unknown>
Signed-off-by: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Corey Ashford <cjashfor@linux.vnet.ibm.com>
Cc: Arnaldo Carvalho de Melo <acme@ghostprotocols.net>
LKML-Reference: <20090408130409.220518450@chello.nl>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
-rw-r--r-- | Documentation/perf_counter/Makefile | 8 | ||||
-rw-r--r-- | Documentation/perf_counter/perf-record.c | 530 | ||||
-rw-r--r-- | Documentation/perf_counter/perf-report.cc | 472 |
3 files changed, 1009 insertions, 1 deletions
diff --git a/Documentation/perf_counter/Makefile b/Documentation/perf_counter/Makefile index 194b66215588..1dd37ee7dbdc 100644 --- a/Documentation/perf_counter/Makefile +++ b/Documentation/perf_counter/Makefile | |||
@@ -1,10 +1,16 @@ | |||
1 | BINS = kerneltop perfstat | 1 | BINS = kerneltop perfstat perf-record perf-report |
2 | 2 | ||
3 | all: $(BINS) | 3 | all: $(BINS) |
4 | 4 | ||
5 | kerneltop: kerneltop.c ../../include/linux/perf_counter.h | 5 | kerneltop: kerneltop.c ../../include/linux/perf_counter.h |
6 | cc -O6 -Wall -lrt -o $@ $< | 6 | cc -O6 -Wall -lrt -o $@ $< |
7 | 7 | ||
8 | perf-record: perf-record.c ../../include/linux/perf_counter.h | ||
9 | cc -O6 -Wall -lrt -o $@ $< | ||
10 | |||
11 | perf-report: perf-report.cc ../../include/linux/perf_counter.h | ||
12 | g++ -O6 -Wall -lrt -o $@ $< | ||
13 | |||
8 | perfstat: kerneltop | 14 | perfstat: kerneltop |
9 | ln -sf kerneltop perfstat | 15 | ln -sf kerneltop perfstat |
10 | 16 | ||
diff --git a/Documentation/perf_counter/perf-record.c b/Documentation/perf_counter/perf-record.c new file mode 100644 index 000000000000..614de7c468b2 --- /dev/null +++ b/Documentation/perf_counter/perf-record.c | |||
@@ -0,0 +1,530 @@ | |||
1 | |||
2 | |||
3 | #define _GNU_SOURCE | ||
4 | #include <sys/types.h> | ||
5 | #include <sys/stat.h> | ||
6 | #include <sys/time.h> | ||
7 | #include <unistd.h> | ||
8 | #include <stdint.h> | ||
9 | #include <stdlib.h> | ||
10 | #include <string.h> | ||
11 | #include <limits.h> | ||
12 | #include <getopt.h> | ||
13 | #include <assert.h> | ||
14 | #include <fcntl.h> | ||
15 | #include <stdio.h> | ||
16 | #include <errno.h> | ||
17 | #include <ctype.h> | ||
18 | #include <time.h> | ||
19 | #include <sched.h> | ||
20 | #include <pthread.h> | ||
21 | |||
22 | #include <sys/syscall.h> | ||
23 | #include <sys/ioctl.h> | ||
24 | #include <sys/poll.h> | ||
25 | #include <sys/prctl.h> | ||
26 | #include <sys/wait.h> | ||
27 | #include <sys/uio.h> | ||
28 | #include <sys/mman.h> | ||
29 | |||
30 | #include <linux/unistd.h> | ||
31 | #include <linux/types.h> | ||
32 | |||
33 | #include "../../include/linux/perf_counter.h" | ||
34 | |||
35 | |||
36 | /* | ||
37 | * prctl(PR_TASK_PERF_COUNTERS_DISABLE) will (cheaply) disable all | ||
38 | * counters in the current task. | ||
39 | */ | ||
40 | #define PR_TASK_PERF_COUNTERS_DISABLE 31 | ||
41 | #define PR_TASK_PERF_COUNTERS_ENABLE 32 | ||
42 | |||
43 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) | ||
44 | |||
45 | #define rdclock() \ | ||
46 | ({ \ | ||
47 | struct timespec ts; \ | ||
48 | \ | ||
49 | clock_gettime(CLOCK_MONOTONIC, &ts); \ | ||
50 | ts.tv_sec * 1000000000ULL + ts.tv_nsec; \ | ||
51 | }) | ||
52 | |||
53 | /* | ||
54 | * Pick up some kernel type conventions: | ||
55 | */ | ||
56 | #define __user | ||
57 | #define asmlinkage | ||
58 | |||
59 | #ifdef __x86_64__ | ||
60 | #define __NR_perf_counter_open 295 | ||
61 | #define rmb() asm volatile("lfence" ::: "memory") | ||
62 | #define cpu_relax() asm volatile("rep; nop" ::: "memory"); | ||
63 | #endif | ||
64 | |||
65 | #ifdef __i386__ | ||
66 | #define __NR_perf_counter_open 333 | ||
67 | #define rmb() asm volatile("lfence" ::: "memory") | ||
68 | #define cpu_relax() asm volatile("rep; nop" ::: "memory"); | ||
69 | #endif | ||
70 | |||
71 | #ifdef __powerpc__ | ||
72 | #define __NR_perf_counter_open 319 | ||
73 | #define rmb() asm volatile ("sync" ::: "memory") | ||
74 | #define cpu_relax() asm volatile ("" ::: "memory"); | ||
75 | #endif | ||
76 | |||
77 | #define unlikely(x) __builtin_expect(!!(x), 0) | ||
78 | #define min(x, y) ({ \ | ||
79 | typeof(x) _min1 = (x); \ | ||
80 | typeof(y) _min2 = (y); \ | ||
81 | (void) (&_min1 == &_min2); \ | ||
82 | _min1 < _min2 ? _min1 : _min2; }) | ||
83 | |||
84 | asmlinkage int sys_perf_counter_open( | ||
85 | struct perf_counter_hw_event *hw_event_uptr __user, | ||
86 | pid_t pid, | ||
87 | int cpu, | ||
88 | int group_fd, | ||
89 | unsigned long flags) | ||
90 | { | ||
91 | return syscall( | ||
92 | __NR_perf_counter_open, hw_event_uptr, pid, cpu, group_fd, flags); | ||
93 | } | ||
94 | |||
95 | #define MAX_COUNTERS 64 | ||
96 | #define MAX_NR_CPUS 256 | ||
97 | |||
98 | #define EID(type, id) (((__u64)(type) << PERF_COUNTER_TYPE_SHIFT) | (id)) | ||
99 | |||
100 | static int nr_counters = 0; | ||
101 | static __u64 event_id[MAX_COUNTERS] = { }; | ||
102 | static int default_interval = 100000; | ||
103 | static int event_count[MAX_COUNTERS]; | ||
104 | static int fd[MAX_NR_CPUS][MAX_COUNTERS]; | ||
105 | static int nr_cpus = 0; | ||
106 | static unsigned int page_size; | ||
107 | static unsigned int mmap_pages = 16; | ||
108 | static int output; | ||
109 | static char *output_name = "output.perf"; | ||
110 | static int group = 0; | ||
111 | static unsigned int realtime_prio = 0; | ||
112 | |||
113 | const unsigned int default_count[] = { | ||
114 | 1000000, | ||
115 | 1000000, | ||
116 | 10000, | ||
117 | 10000, | ||
118 | 1000000, | ||
119 | 10000, | ||
120 | }; | ||
121 | |||
122 | static char *hw_event_names[] = { | ||
123 | "CPU cycles", | ||
124 | "instructions", | ||
125 | "cache references", | ||
126 | "cache misses", | ||
127 | "branches", | ||
128 | "branch misses", | ||
129 | "bus cycles", | ||
130 | }; | ||
131 | |||
132 | static char *sw_event_names[] = { | ||
133 | "cpu clock ticks", | ||
134 | "task clock ticks", | ||
135 | "pagefaults", | ||
136 | "context switches", | ||
137 | "CPU migrations", | ||
138 | "minor faults", | ||
139 | "major faults", | ||
140 | }; | ||
141 | |||
142 | struct event_symbol { | ||
143 | __u64 event; | ||
144 | char *symbol; | ||
145 | }; | ||
146 | |||
147 | static struct event_symbol event_symbols[] = { | ||
148 | {EID(PERF_TYPE_HARDWARE, PERF_COUNT_CPU_CYCLES), "cpu-cycles", }, | ||
149 | {EID(PERF_TYPE_HARDWARE, PERF_COUNT_CPU_CYCLES), "cycles", }, | ||
150 | {EID(PERF_TYPE_HARDWARE, PERF_COUNT_INSTRUCTIONS), "instructions", }, | ||
151 | {EID(PERF_TYPE_HARDWARE, PERF_COUNT_CACHE_REFERENCES), "cache-references", }, | ||
152 | {EID(PERF_TYPE_HARDWARE, PERF_COUNT_CACHE_MISSES), "cache-misses", }, | ||
153 | {EID(PERF_TYPE_HARDWARE, PERF_COUNT_BRANCH_INSTRUCTIONS), "branch-instructions", }, | ||
154 | {EID(PERF_TYPE_HARDWARE, PERF_COUNT_BRANCH_INSTRUCTIONS), "branches", }, | ||
155 | {EID(PERF_TYPE_HARDWARE, PERF_COUNT_BRANCH_MISSES), "branch-misses", }, | ||
156 | {EID(PERF_TYPE_HARDWARE, PERF_COUNT_BUS_CYCLES), "bus-cycles", }, | ||
157 | |||
158 | {EID(PERF_TYPE_SOFTWARE, PERF_COUNT_CPU_CLOCK), "cpu-clock", }, | ||
159 | {EID(PERF_TYPE_SOFTWARE, PERF_COUNT_TASK_CLOCK), "task-clock", }, | ||
160 | {EID(PERF_TYPE_SOFTWARE, PERF_COUNT_PAGE_FAULTS), "page-faults", }, | ||
161 | {EID(PERF_TYPE_SOFTWARE, PERF_COUNT_PAGE_FAULTS), "faults", }, | ||
162 | {EID(PERF_TYPE_SOFTWARE, PERF_COUNT_PAGE_FAULTS_MIN), "minor-faults", }, | ||
163 | {EID(PERF_TYPE_SOFTWARE, PERF_COUNT_PAGE_FAULTS_MAJ), "major-faults", }, | ||
164 | {EID(PERF_TYPE_SOFTWARE, PERF_COUNT_CONTEXT_SWITCHES), "context-switches", }, | ||
165 | {EID(PERF_TYPE_SOFTWARE, PERF_COUNT_CONTEXT_SWITCHES), "cs", }, | ||
166 | {EID(PERF_TYPE_SOFTWARE, PERF_COUNT_CPU_MIGRATIONS), "cpu-migrations", }, | ||
167 | {EID(PERF_TYPE_SOFTWARE, PERF_COUNT_CPU_MIGRATIONS), "migrations", }, | ||
168 | }; | ||
169 | |||
170 | /* | ||
171 | * Each event can have multiple symbolic names. | ||
172 | * Symbolic names are (almost) exactly matched. | ||
173 | */ | ||
174 | static __u64 match_event_symbols(char *str) | ||
175 | { | ||
176 | __u64 config, id; | ||
177 | int type; | ||
178 | unsigned int i; | ||
179 | |||
180 | if (sscanf(str, "r%llx", &config) == 1) | ||
181 | return config | PERF_COUNTER_RAW_MASK; | ||
182 | |||
183 | if (sscanf(str, "%d:%llu", &type, &id) == 2) | ||
184 | return EID(type, id); | ||
185 | |||
186 | for (i = 0; i < ARRAY_SIZE(event_symbols); i++) { | ||
187 | if (!strncmp(str, event_symbols[i].symbol, | ||
188 | strlen(event_symbols[i].symbol))) | ||
189 | return event_symbols[i].event; | ||
190 | } | ||
191 | |||
192 | return ~0ULL; | ||
193 | } | ||
194 | |||
195 | static int parse_events(char *str) | ||
196 | { | ||
197 | __u64 config; | ||
198 | |||
199 | again: | ||
200 | if (nr_counters == MAX_COUNTERS) | ||
201 | return -1; | ||
202 | |||
203 | config = match_event_symbols(str); | ||
204 | if (config == ~0ULL) | ||
205 | return -1; | ||
206 | |||
207 | event_id[nr_counters] = config; | ||
208 | nr_counters++; | ||
209 | |||
210 | str = strstr(str, ","); | ||
211 | if (str) { | ||
212 | str++; | ||
213 | goto again; | ||
214 | } | ||
215 | |||
216 | return 0; | ||
217 | } | ||
218 | |||
219 | #define __PERF_COUNTER_FIELD(config, name) \ | ||
220 | ((config & PERF_COUNTER_##name##_MASK) >> PERF_COUNTER_##name##_SHIFT) | ||
221 | |||
222 | #define PERF_COUNTER_RAW(config) __PERF_COUNTER_FIELD(config, RAW) | ||
223 | #define PERF_COUNTER_CONFIG(config) __PERF_COUNTER_FIELD(config, CONFIG) | ||
224 | #define PERF_COUNTER_TYPE(config) __PERF_COUNTER_FIELD(config, TYPE) | ||
225 | #define PERF_COUNTER_ID(config) __PERF_COUNTER_FIELD(config, EVENT) | ||
226 | |||
227 | static void display_events_help(void) | ||
228 | { | ||
229 | unsigned int i; | ||
230 | __u64 e; | ||
231 | |||
232 | printf( | ||
233 | " -e EVENT --event=EVENT # symbolic-name abbreviations"); | ||
234 | |||
235 | for (i = 0; i < ARRAY_SIZE(event_symbols); i++) { | ||
236 | int type, id; | ||
237 | |||
238 | e = event_symbols[i].event; | ||
239 | type = PERF_COUNTER_TYPE(e); | ||
240 | id = PERF_COUNTER_ID(e); | ||
241 | |||
242 | printf("\n %d:%d: %-20s", | ||
243 | type, id, event_symbols[i].symbol); | ||
244 | } | ||
245 | |||
246 | printf("\n" | ||
247 | " rNNN: raw PMU events (eventsel+umask)\n\n"); | ||
248 | } | ||
249 | |||
250 | static void display_help(void) | ||
251 | { | ||
252 | printf( | ||
253 | "Usage: perf-record [<options>]\n" | ||
254 | "perf-record Options (up to %d event types can be specified at once):\n\n", | ||
255 | MAX_COUNTERS); | ||
256 | |||
257 | display_events_help(); | ||
258 | |||
259 | printf( | ||
260 | " -c CNT --count=CNT # event period to sample\n" | ||
261 | " -m pages --mmap_pages=<pages> # number of mmap data pages\n" | ||
262 | " -o file --output=<file> # output file\n" | ||
263 | " -r prio --realtime=<prio> # use RT prio\n" | ||
264 | ); | ||
265 | |||
266 | exit(0); | ||
267 | } | ||
268 | |||
269 | static void process_options(int argc, char *argv[]) | ||
270 | { | ||
271 | int error = 0, counter; | ||
272 | |||
273 | for (;;) { | ||
274 | int option_index = 0; | ||
275 | /** Options for getopt */ | ||
276 | static struct option long_options[] = { | ||
277 | {"count", required_argument, NULL, 'c'}, | ||
278 | {"event", required_argument, NULL, 'e'}, | ||
279 | {"mmap_pages", required_argument, NULL, 'm'}, | ||
280 | {"output", required_argument, NULL, 'o'}, | ||
281 | {"realtime", required_argument, NULL, 'r'}, | ||
282 | {NULL, 0, NULL, 0 } | ||
283 | }; | ||
284 | int c = getopt_long(argc, argv, "+:c:e:m:o:r:", | ||
285 | long_options, &option_index); | ||
286 | if (c == -1) | ||
287 | break; | ||
288 | |||
289 | switch (c) { | ||
290 | case 'c': default_interval = atoi(optarg); break; | ||
291 | case 'e': error = parse_events(optarg); break; | ||
292 | case 'm': mmap_pages = atoi(optarg); break; | ||
293 | case 'o': output_name = strdup(optarg); break; | ||
294 | case 'r': realtime_prio = atoi(optarg); break; | ||
295 | default: error = 1; break; | ||
296 | } | ||
297 | } | ||
298 | if (error) | ||
299 | display_help(); | ||
300 | |||
301 | if (!nr_counters) { | ||
302 | nr_counters = 1; | ||
303 | event_id[0] = 0; | ||
304 | } | ||
305 | |||
306 | for (counter = 0; counter < nr_counters; counter++) { | ||
307 | if (event_count[counter]) | ||
308 | continue; | ||
309 | |||
310 | event_count[counter] = default_interval; | ||
311 | } | ||
312 | } | ||
313 | |||
314 | struct mmap_data { | ||
315 | int counter; | ||
316 | void *base; | ||
317 | unsigned int mask; | ||
318 | unsigned int prev; | ||
319 | }; | ||
320 | |||
321 | static unsigned int mmap_read_head(struct mmap_data *md) | ||
322 | { | ||
323 | struct perf_counter_mmap_page *pc = md->base; | ||
324 | int head; | ||
325 | |||
326 | head = pc->data_head; | ||
327 | rmb(); | ||
328 | |||
329 | return head; | ||
330 | } | ||
331 | |||
332 | static long events; | ||
333 | static struct timeval last_read, this_read; | ||
334 | |||
335 | static void mmap_read(struct mmap_data *md) | ||
336 | { | ||
337 | unsigned int head = mmap_read_head(md); | ||
338 | unsigned int old = md->prev; | ||
339 | unsigned char *data = md->base + page_size; | ||
340 | unsigned long size; | ||
341 | void *buf; | ||
342 | int diff; | ||
343 | |||
344 | gettimeofday(&this_read, NULL); | ||
345 | |||
346 | /* | ||
347 | * If we're further behind than half the buffer, there's a chance | ||
348 | * the writer will bite our tail and screw up the events under us. | ||
349 | * | ||
350 | * If we somehow ended up ahead of the head, we got messed up. | ||
351 | * | ||
352 | * In either case, truncate and restart at head. | ||
353 | */ | ||
354 | diff = head - old; | ||
355 | if (diff > md->mask / 2 || diff < 0) { | ||
356 | struct timeval iv; | ||
357 | unsigned long msecs; | ||
358 | |||
359 | timersub(&this_read, &last_read, &iv); | ||
360 | msecs = iv.tv_sec*1000 + iv.tv_usec/1000; | ||
361 | |||
362 | fprintf(stderr, "WARNING: failed to keep up with mmap data." | ||
363 | " Last read %lu msecs ago.\n", msecs); | ||
364 | |||
365 | /* | ||
366 | * head points to a known good entry, start there. | ||
367 | */ | ||
368 | old = head; | ||
369 | } | ||
370 | |||
371 | last_read = this_read; | ||
372 | |||
373 | if (old != head) | ||
374 | events++; | ||
375 | |||
376 | size = head - old; | ||
377 | |||
378 | if ((old & md->mask) + size != (head & md->mask)) { | ||
379 | buf = &data[old & md->mask]; | ||
380 | size = md->mask + 1 - (old & md->mask); | ||
381 | old += size; | ||
382 | while (size) { | ||
383 | int ret = write(output, buf, size); | ||
384 | if (ret < 0) { | ||
385 | perror("failed to write"); | ||
386 | exit(-1); | ||
387 | } | ||
388 | size -= ret; | ||
389 | buf += ret; | ||
390 | } | ||
391 | } | ||
392 | |||
393 | buf = &data[old & md->mask]; | ||
394 | size = head - old; | ||
395 | old += size; | ||
396 | while (size) { | ||
397 | int ret = write(output, buf, size); | ||
398 | if (ret < 0) { | ||
399 | perror("failed to write"); | ||
400 | exit(-1); | ||
401 | } | ||
402 | size -= ret; | ||
403 | buf += ret; | ||
404 | } | ||
405 | |||
406 | md->prev = old; | ||
407 | } | ||
408 | |||
409 | static volatile int done = 0; | ||
410 | |||
411 | static void sigchld_handler(int sig) | ||
412 | { | ||
413 | if (sig == SIGCHLD) | ||
414 | done = 1; | ||
415 | } | ||
416 | |||
417 | int main(int argc, char *argv[]) | ||
418 | { | ||
419 | struct pollfd event_array[MAX_NR_CPUS * MAX_COUNTERS]; | ||
420 | struct mmap_data mmap_array[MAX_NR_CPUS][MAX_COUNTERS]; | ||
421 | struct perf_counter_hw_event hw_event; | ||
422 | int i, counter, group_fd, nr_poll = 0; | ||
423 | pid_t pid; | ||
424 | int ret; | ||
425 | |||
426 | page_size = sysconf(_SC_PAGE_SIZE); | ||
427 | |||
428 | process_options(argc, argv); | ||
429 | |||
430 | nr_cpus = sysconf(_SC_NPROCESSORS_ONLN); | ||
431 | assert(nr_cpus <= MAX_NR_CPUS); | ||
432 | assert(nr_cpus >= 0); | ||
433 | |||
434 | output = open(output_name, O_CREAT|O_RDWR, S_IRWXU); | ||
435 | if (output < 0) { | ||
436 | perror("failed to create output file"); | ||
437 | exit(-1); | ||
438 | } | ||
439 | |||
440 | argc -= optind; | ||
441 | argv += optind; | ||
442 | |||
443 | for (i = 0; i < nr_cpus; i++) { | ||
444 | group_fd = -1; | ||
445 | for (counter = 0; counter < nr_counters; counter++) { | ||
446 | |||
447 | memset(&hw_event, 0, sizeof(hw_event)); | ||
448 | hw_event.config = event_id[counter]; | ||
449 | hw_event.irq_period = event_count[counter]; | ||
450 | hw_event.record_type = PERF_RECORD_IP | PERF_RECORD_TID; | ||
451 | hw_event.nmi = 1; | ||
452 | hw_event.mmap = 1; | ||
453 | hw_event.comm = 1; | ||
454 | |||
455 | fd[i][counter] = sys_perf_counter_open(&hw_event, -1, i, group_fd, 0); | ||
456 | if (fd[i][counter] < 0) { | ||
457 | int err = errno; | ||
458 | printf("kerneltop error: syscall returned with %d (%s)\n", | ||
459 | fd[i][counter], strerror(err)); | ||
460 | if (err == EPERM) | ||
461 | printf("Are you root?\n"); | ||
462 | exit(-1); | ||
463 | } | ||
464 | assert(fd[i][counter] >= 0); | ||
465 | fcntl(fd[i][counter], F_SETFL, O_NONBLOCK); | ||
466 | |||
467 | /* | ||
468 | * First counter acts as the group leader: | ||
469 | */ | ||
470 | if (group && group_fd == -1) | ||
471 | group_fd = fd[i][counter]; | ||
472 | |||
473 | event_array[nr_poll].fd = fd[i][counter]; | ||
474 | event_array[nr_poll].events = POLLIN; | ||
475 | nr_poll++; | ||
476 | |||
477 | mmap_array[i][counter].counter = counter; | ||
478 | mmap_array[i][counter].prev = 0; | ||
479 | mmap_array[i][counter].mask = mmap_pages*page_size - 1; | ||
480 | mmap_array[i][counter].base = mmap(NULL, (mmap_pages+1)*page_size, | ||
481 | PROT_READ, MAP_SHARED, fd[i][counter], 0); | ||
482 | if (mmap_array[i][counter].base == MAP_FAILED) { | ||
483 | printf("kerneltop error: failed to mmap with %d (%s)\n", | ||
484 | errno, strerror(errno)); | ||
485 | exit(-1); | ||
486 | } | ||
487 | } | ||
488 | } | ||
489 | |||
490 | signal(SIGCHLD, sigchld_handler); | ||
491 | |||
492 | pid = fork(); | ||
493 | if (pid < 0) | ||
494 | perror("failed to fork"); | ||
495 | |||
496 | if (!pid) { | ||
497 | if (execvp(argv[0], argv)) { | ||
498 | perror(argv[0]); | ||
499 | exit(-1); | ||
500 | } | ||
501 | } | ||
502 | |||
503 | if (realtime_prio) { | ||
504 | struct sched_param param; | ||
505 | |||
506 | param.sched_priority = realtime_prio; | ||
507 | if (sched_setscheduler(0, SCHED_FIFO, ¶m)) { | ||
508 | printf("Could not set realtime priority.\n"); | ||
509 | exit(-1); | ||
510 | } | ||
511 | } | ||
512 | |||
513 | /* | ||
514 | * TODO: store the current /proc/$/maps information somewhere | ||
515 | */ | ||
516 | |||
517 | while (!done) { | ||
518 | int hits = events; | ||
519 | |||
520 | for (i = 0; i < nr_cpus; i++) { | ||
521 | for (counter = 0; counter < nr_counters; counter++) | ||
522 | mmap_read(&mmap_array[i][counter]); | ||
523 | } | ||
524 | |||
525 | if (hits == events) | ||
526 | ret = poll(event_array, nr_poll, 100); | ||
527 | } | ||
528 | |||
529 | return 0; | ||
530 | } | ||
diff --git a/Documentation/perf_counter/perf-report.cc b/Documentation/perf_counter/perf-report.cc new file mode 100644 index 000000000000..09da0ba482cd --- /dev/null +++ b/Documentation/perf_counter/perf-report.cc | |||
@@ -0,0 +1,472 @@ | |||
1 | #define _GNU_SOURCE | ||
2 | #include <sys/types.h> | ||
3 | #include <sys/stat.h> | ||
4 | #include <sys/time.h> | ||
5 | #include <unistd.h> | ||
6 | #include <stdint.h> | ||
7 | #include <stdlib.h> | ||
8 | #include <string.h> | ||
9 | #include <limits.h> | ||
10 | #include <fcntl.h> | ||
11 | #include <stdio.h> | ||
12 | #include <errno.h> | ||
13 | #include <ctype.h> | ||
14 | #include <time.h> | ||
15 | #include <getopt.h> | ||
16 | |||
17 | #include <sys/ioctl.h> | ||
18 | #include <sys/poll.h> | ||
19 | #include <sys/prctl.h> | ||
20 | #include <sys/wait.h> | ||
21 | #include <sys/mman.h> | ||
22 | #include <sys/types.h> | ||
23 | #include <sys/stat.h> | ||
24 | |||
25 | #include <linux/unistd.h> | ||
26 | #include <linux/types.h> | ||
27 | |||
28 | #include "../../include/linux/perf_counter.h" | ||
29 | |||
30 | #include <set> | ||
31 | #include <map> | ||
32 | #include <string> | ||
33 | |||
34 | |||
35 | static char const *input_name = "output.perf"; | ||
36 | static int input; | ||
37 | |||
38 | static unsigned long page_size; | ||
39 | static unsigned long mmap_window = 32; | ||
40 | |||
41 | struct ip_event { | ||
42 | struct perf_event_header header; | ||
43 | __u64 ip; | ||
44 | __u32 pid, tid; | ||
45 | }; | ||
46 | struct mmap_event { | ||
47 | struct perf_event_header header; | ||
48 | __u32 pid, tid; | ||
49 | __u64 start; | ||
50 | __u64 len; | ||
51 | __u64 pgoff; | ||
52 | char filename[PATH_MAX]; | ||
53 | }; | ||
54 | struct comm_event { | ||
55 | struct perf_event_header header; | ||
56 | __u32 pid,tid; | ||
57 | char comm[16]; | ||
58 | }; | ||
59 | |||
60 | typedef union event_union { | ||
61 | struct perf_event_header header; | ||
62 | struct ip_event ip; | ||
63 | struct mmap_event mmap; | ||
64 | struct comm_event comm; | ||
65 | } event_t; | ||
66 | |||
67 | struct section { | ||
68 | uint64_t start; | ||
69 | uint64_t end; | ||
70 | |||
71 | uint64_t offset; | ||
72 | |||
73 | std::string name; | ||
74 | |||
75 | section() { }; | ||
76 | |||
77 | section(uint64_t stab) : end(stab) { }; | ||
78 | |||
79 | section(uint64_t start, uint64_t size, uint64_t offset, std::string name) : | ||
80 | start(start), end(start + size), offset(offset), name(name) | ||
81 | { }; | ||
82 | |||
83 | bool operator < (const struct section &s) const { | ||
84 | return end < s.end; | ||
85 | }; | ||
86 | }; | ||
87 | |||
88 | typedef std::set<struct section> sections_t; | ||
89 | |||
90 | struct symbol { | ||
91 | uint64_t start; | ||
92 | uint64_t end; | ||
93 | |||
94 | std::string name; | ||
95 | |||
96 | symbol() { }; | ||
97 | |||
98 | symbol(uint64_t ip) : start(ip) { } | ||
99 | |||
100 | symbol(uint64_t start, uint64_t len, std::string name) : | ||
101 | start(start), end(start + len), name(name) | ||
102 | { }; | ||
103 | |||
104 | bool operator < (const struct symbol &s) const { | ||
105 | return start < s.start; | ||
106 | }; | ||
107 | }; | ||
108 | |||
109 | typedef std::set<struct symbol> symbols_t; | ||
110 | |||
111 | struct dso { | ||
112 | sections_t sections; | ||
113 | symbols_t syms; | ||
114 | }; | ||
115 | |||
116 | static std::map<std::string, struct dso> dsos; | ||
117 | |||
118 | static void load_dso_sections(std::string dso_name) | ||
119 | { | ||
120 | struct dso &dso = dsos[dso_name]; | ||
121 | |||
122 | std::string cmd = "readelf -DSW " + dso_name; | ||
123 | |||
124 | FILE *file = popen(cmd.c_str(), "r"); | ||
125 | if (!file) { | ||
126 | perror("failed to open pipe"); | ||
127 | exit(-1); | ||
128 | } | ||
129 | |||
130 | char *line = NULL; | ||
131 | size_t n = 0; | ||
132 | |||
133 | while (!feof(file)) { | ||
134 | uint64_t addr, off, size; | ||
135 | char name[32]; | ||
136 | |||
137 | if (getline(&line, &n, file) < 0) | ||
138 | break; | ||
139 | if (!line) | ||
140 | break; | ||
141 | |||
142 | if (sscanf(line, " [%*2d] %16s %*14s %Lx %Lx %Lx", | ||
143 | name, &addr, &off, &size) == 4) { | ||
144 | |||
145 | dso.sections.insert(section(addr, size, addr - off, name)); | ||
146 | } | ||
147 | #if 0 | ||
148 | /* | ||
149 | * for reading readelf symbols (-s), however these don't seem | ||
150 | * to include nearly everything, so use nm for that. | ||
151 | */ | ||
152 | if (sscanf(line, " %*4d %*3d: %Lx %5Lu %*7s %*6s %*7s %3d %s", | ||
153 | &start, &size, §ion, sym) == 4) { | ||
154 | |||
155 | start -= dso.section_offsets[section]; | ||
156 | |||
157 | dso.syms.insert(symbol(start, size, std::string(sym))); | ||
158 | } | ||
159 | #endif | ||
160 | } | ||
161 | pclose(file); | ||
162 | } | ||
163 | |||
164 | static void load_dso_symbols(std::string dso_name, std::string args) | ||
165 | { | ||
166 | struct dso &dso = dsos[dso_name]; | ||
167 | |||
168 | std::string cmd = "nm -nSC " + args + " " + dso_name; | ||
169 | |||
170 | FILE *file = popen(cmd.c_str(), "r"); | ||
171 | if (!file) { | ||
172 | perror("failed to open pipe"); | ||
173 | exit(-1); | ||
174 | } | ||
175 | |||
176 | char *line = NULL; | ||
177 | size_t n = 0; | ||
178 | |||
179 | while (!feof(file)) { | ||
180 | uint64_t start, size; | ||
181 | char c; | ||
182 | char sym[1024]; | ||
183 | |||
184 | if (getline(&line, &n, file) < 0) | ||
185 | break; | ||
186 | if (!line) | ||
187 | break; | ||
188 | |||
189 | |||
190 | if (sscanf(line, "%Lx %Lx %c %s", &start, &size, &c, sym) == 4) { | ||
191 | sections_t::const_iterator si = | ||
192 | dso.sections.upper_bound(section(start)); | ||
193 | if (si == dso.sections.end()) { | ||
194 | printf("symbol in unknown section: %s\n", sym); | ||
195 | continue; | ||
196 | } | ||
197 | |||
198 | start -= si->offset; | ||
199 | |||
200 | dso.syms.insert(symbol(start, size, sym)); | ||
201 | } | ||
202 | } | ||
203 | pclose(file); | ||
204 | } | ||
205 | |||
206 | static void load_dso(std::string dso_name) | ||
207 | { | ||
208 | load_dso_sections(dso_name); | ||
209 | load_dso_symbols(dso_name, "-D"); /* dynamic symbols */ | ||
210 | load_dso_symbols(dso_name, ""); /* regular ones */ | ||
211 | } | ||
212 | |||
213 | void load_kallsyms(void) | ||
214 | { | ||
215 | struct dso &dso = dsos["[kernel]"]; | ||
216 | |||
217 | FILE *file = fopen("/proc/kallsyms", "r"); | ||
218 | if (!file) { | ||
219 | perror("failed to open kallsyms"); | ||
220 | exit(-1); | ||
221 | } | ||
222 | |||
223 | char *line; | ||
224 | size_t n; | ||
225 | |||
226 | while (!feof(file)) { | ||
227 | uint64_t start; | ||
228 | char c; | ||
229 | char sym[1024]; | ||
230 | |||
231 | if (getline(&line, &n, file) < 0) | ||
232 | break; | ||
233 | if (!line) | ||
234 | break; | ||
235 | |||
236 | if (sscanf(line, "%Lx %c %s", &start, &c, sym) == 3) | ||
237 | dso.syms.insert(symbol(start, 0x1000000, std::string(sym))); | ||
238 | } | ||
239 | fclose(file); | ||
240 | } | ||
241 | |||
242 | struct map { | ||
243 | uint64_t start; | ||
244 | uint64_t end; | ||
245 | uint64_t pgoff; | ||
246 | |||
247 | std::string dso; | ||
248 | |||
249 | map() { }; | ||
250 | |||
251 | map(uint64_t ip) : end(ip) { } | ||
252 | |||
253 | map(mmap_event *mmap) { | ||
254 | start = mmap->start; | ||
255 | end = mmap->start + mmap->len; | ||
256 | pgoff = mmap->pgoff; | ||
257 | |||
258 | dso = std::string(mmap->filename); | ||
259 | |||
260 | if (dsos.find(dso) == dsos.end()) | ||
261 | load_dso(dso); | ||
262 | }; | ||
263 | |||
264 | bool operator < (const struct map &m) const { | ||
265 | return end < m.end; | ||
266 | }; | ||
267 | }; | ||
268 | |||
269 | typedef std::set<struct map> maps_t; | ||
270 | |||
271 | static std::map<int, maps_t> maps; | ||
272 | |||
273 | static std::map<int, std::string> comms; | ||
274 | |||
275 | static std::map<std::string, int> hist; | ||
276 | static std::multimap<int, std::string> rev_hist; | ||
277 | |||
278 | static std::string resolve_comm(int pid) | ||
279 | { | ||
280 | std::string comm = "<unknown>"; | ||
281 | std::map<int, std::string>::const_iterator ci = comms.find(pid); | ||
282 | if (ci != comms.end()) | ||
283 | comm = ci->second; | ||
284 | |||
285 | return comm; | ||
286 | } | ||
287 | |||
288 | static std::string resolve_user_symbol(int pid, uint64_t ip) | ||
289 | { | ||
290 | std::string sym = "<unknown>"; | ||
291 | |||
292 | maps_t &m = maps[pid]; | ||
293 | maps_t::const_iterator mi = m.upper_bound(map(ip)); | ||
294 | if (mi == m.end()) | ||
295 | return sym; | ||
296 | |||
297 | ip -= mi->start + mi->pgoff; | ||
298 | |||
299 | symbols_t &s = dsos[mi->dso].syms; | ||
300 | symbols_t::const_iterator si = s.upper_bound(symbol(ip)); | ||
301 | |||
302 | sym = mi->dso + ": <unknown>"; | ||
303 | |||
304 | if (si == s.begin()) | ||
305 | return sym; | ||
306 | si--; | ||
307 | |||
308 | if (si->start <= ip && ip < si->end) | ||
309 | sym = mi->dso + ": " + si->name; | ||
310 | #if 0 | ||
311 | else if (si->start <= ip) | ||
312 | sym = mi->dso + ": ?" + si->name; | ||
313 | #endif | ||
314 | |||
315 | return sym; | ||
316 | } | ||
317 | |||
318 | static std::string resolve_kernel_symbol(uint64_t ip) | ||
319 | { | ||
320 | std::string sym = "<unknown>"; | ||
321 | |||
322 | symbols_t &s = dsos["[kernel]"].syms; | ||
323 | symbols_t::const_iterator si = s.upper_bound(symbol(ip)); | ||
324 | |||
325 | if (si == s.begin()) | ||
326 | return sym; | ||
327 | si--; | ||
328 | |||
329 | if (si->start <= ip && ip < si->end) | ||
330 | sym = si->name; | ||
331 | |||
332 | return sym; | ||
333 | } | ||
334 | |||
335 | static void display_help(void) | ||
336 | { | ||
337 | printf( | ||
338 | "Usage: perf-report [<options>]\n" | ||
339 | " -i file --input=<file> # input file\n" | ||
340 | ); | ||
341 | |||
342 | exit(0); | ||
343 | } | ||
344 | |||
345 | static void process_options(int argc, char *argv[]) | ||
346 | { | ||
347 | int error = 0; | ||
348 | |||
349 | for (;;) { | ||
350 | int option_index = 0; | ||
351 | /** Options for getopt */ | ||
352 | static struct option long_options[] = { | ||
353 | {"input", required_argument, NULL, 'i'}, | ||
354 | {NULL, 0, NULL, 0 } | ||
355 | }; | ||
356 | int c = getopt_long(argc, argv, "+:i:", | ||
357 | long_options, &option_index); | ||
358 | if (c == -1) | ||
359 | break; | ||
360 | |||
361 | switch (c) { | ||
362 | case 'i': input_name = strdup(optarg); break; | ||
363 | default: error = 1; break; | ||
364 | } | ||
365 | } | ||
366 | |||
367 | if (error) | ||
368 | display_help(); | ||
369 | } | ||
370 | |||
371 | int main(int argc, char *argv[]) | ||
372 | { | ||
373 | unsigned long offset = 0; | ||
374 | unsigned long head = 0; | ||
375 | struct stat stat; | ||
376 | char *buf; | ||
377 | event_t *event; | ||
378 | int ret; | ||
379 | unsigned long total = 0; | ||
380 | |||
381 | page_size = getpagesize(); | ||
382 | |||
383 | process_options(argc, argv); | ||
384 | |||
385 | input = open(input_name, O_RDONLY); | ||
386 | if (input < 0) { | ||
387 | perror("failed to open file"); | ||
388 | exit(-1); | ||
389 | } | ||
390 | |||
391 | ret = fstat(input, &stat); | ||
392 | if (ret < 0) { | ||
393 | perror("failed to stat file"); | ||
394 | exit(-1); | ||
395 | } | ||
396 | |||
397 | load_kallsyms(); | ||
398 | |||
399 | remap: | ||
400 | buf = (char *)mmap(NULL, page_size * mmap_window, PROT_READ, | ||
401 | MAP_SHARED, input, offset); | ||
402 | if (buf == MAP_FAILED) { | ||
403 | perror("failed to mmap file"); | ||
404 | exit(-1); | ||
405 | } | ||
406 | |||
407 | more: | ||
408 | event = (event_t *)(buf + head); | ||
409 | |||
410 | if (head + event->header.size >= page_size * mmap_window) { | ||
411 | unsigned long shift = page_size * (head / page_size); | ||
412 | |||
413 | munmap(buf, page_size * mmap_window); | ||
414 | offset += shift; | ||
415 | head -= shift; | ||
416 | goto remap; | ||
417 | } | ||
418 | head += event->header.size; | ||
419 | |||
420 | if (event->header.misc & PERF_EVENT_MISC_OVERFLOW) { | ||
421 | std::string comm, sym, level; | ||
422 | char output[1024]; | ||
423 | |||
424 | if (event->header.misc & PERF_EVENT_MISC_KERNEL) { | ||
425 | level = "[kernel]"; | ||
426 | sym = resolve_kernel_symbol(event->ip.ip); | ||
427 | } else if (event->header.misc & PERF_EVENT_MISC_USER) { | ||
428 | level = "[ user ]"; | ||
429 | sym = resolve_user_symbol(event->ip.pid, event->ip.ip); | ||
430 | } else { | ||
431 | level = "[ hv ]"; | ||
432 | } | ||
433 | comm = resolve_comm(event->ip.pid); | ||
434 | |||
435 | snprintf(output, sizeof(output), "%16s %s %s", | ||
436 | comm.c_str(), level.c_str(), sym.c_str()); | ||
437 | hist[output]++; | ||
438 | |||
439 | total++; | ||
440 | |||
441 | } else switch (event->header.type) { | ||
442 | case PERF_EVENT_MMAP: | ||
443 | maps[event->mmap.pid].insert(map(&event->mmap)); | ||
444 | break; | ||
445 | |||
446 | case PERF_EVENT_COMM: | ||
447 | comms[event->comm.pid] = std::string(event->comm.comm); | ||
448 | break; | ||
449 | } | ||
450 | |||
451 | if (offset + head < stat.st_size) | ||
452 | goto more; | ||
453 | |||
454 | close(input); | ||
455 | |||
456 | std::map<std::string, int>::iterator hi = hist.begin(); | ||
457 | |||
458 | while (hi != hist.end()) { | ||
459 | rev_hist.insert(std::pair<int, std::string>(hi->second, hi->first)); | ||
460 | hist.erase(hi++); | ||
461 | } | ||
462 | |||
463 | std::multimap<int, std::string>::const_iterator ri = rev_hist.begin(); | ||
464 | |||
465 | while (ri != rev_hist.end()) { | ||
466 | printf(" %5.2f %s\n", (100.0 * ri->first)/total, ri->second.c_str()); | ||
467 | ri++; | ||
468 | } | ||
469 | |||
470 | return 0; | ||
471 | } | ||
472 | |||