perf: Track the perf_context when converting
[akaros.git] / tools / profile / perf / perfconv.c
1 /* Copyright (c) 2015 Google Inc
2  * Davide Libenzi <dlibenzi@google.com>
3  * See LICENSE for details.
4  *
5  * Converts kprof profiler files into Linux perf ones. The Linux Perf file
6  * format has bee illustrated here:
7  *
8  *       https://lwn.net/Articles/644919/
9  *       https://openlab-mu-internal.web.cern.ch/openlab-mu-internal/03_Documents/
10  *                       3_Technical_Documents/Technical_Reports/2011/Urs_Fassler_report.pdf
11  *
12  */
13
14 #include <ros/common.h>
15 #include <ros/memops.h>
16 #include <ros/profiler_records.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <stdint.h>
22 #include <unistd.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <stdarg.h>
26 #include "perf_format.h"
27 #include "xlib.h"
28 #include "perf_core.h"
29 #include "perfconv.h"
30
31 #define MAX_PERF_RECORD_SIZE (32 * 1024 * 1024)
32 #define PERF_RECORD_BUFFER_SIZE 1024
33 #define OFFSET_NORELOC ((uint64_t) -1)
34 #define MEMFILE_BLOCK_SIZE (64 * 1024)
35
36 #define MBWR_SOLID (1 << 0)
37
38 struct perf_record {
39         uint64_t type;
40         uint64_t size;
41         char *data;
42         char buffer[PERF_RECORD_BUFFER_SIZE];
43 };
44
45 static void dbg_print(struct perfconv_context *cctx, int level, FILE *file,
46                                           const char *fmt, ...)
47 {
48         if (cctx->debug_level >= level) {
49                 va_list args;
50
51                 va_start(args, fmt);
52                 vfprintf(file, fmt, args);
53                 va_end(args);
54         }
55 }
56
57 void perfconv_set_dbglevel(int level, struct perfconv_context *cctx)
58 {
59         cctx->debug_level = level;
60 }
61
62 static void free_record(struct perf_record *pr)
63 {
64         if (pr->data != pr->buffer)
65                 free(pr->data);
66         pr->data = NULL;
67 }
68
69 static int read_record(FILE *file, struct perf_record *pr)
70 {
71         if (vb_fdecode_uint64(file, &pr->type) == EOF ||
72                 vb_fdecode_uint64(file, &pr->size) == EOF)
73                 return EOF;
74         if (pr->size > MAX_PERF_RECORD_SIZE) {
75                 fprintf(stderr, "Invalid record size: type=%lu size=%lu\n", pr->type,
76                                 pr->size);
77                 exit(1);
78         }
79         if (pr->size > sizeof(pr->buffer))
80                 pr->data = xmalloc((size_t) pr->size);
81         else
82                 pr->data = pr->buffer;
83         if (fread(pr->data, 1, (size_t) pr->size, file) != (size_t) pr->size) {
84                 fprintf(stderr, "Unable to read record memory: size=%lu\n",
85                                 pr->size);
86                 return EOF;
87         }
88
89         return 0;
90 }
91
92 static struct mem_block *mem_block_alloc(struct mem_file *mf, size_t size)
93 {
94         struct mem_block *mb = xmem_arena_alloc(mf->ma,
95                                                                                         sizeof(struct mem_block) + size);
96
97         mb->next = NULL;
98         mb->base = mb->wptr = (char *) mb + sizeof(struct mem_block);
99         mb->top = mb->base + size;
100
101         return mb;
102 }
103
104 static char *mem_block_write(struct mem_block *mb, const void *data,
105                                                          size_t size)
106 {
107         char *wrbase = mb->wptr;
108
109         always_assert(size <= mb->top - mb->wptr);
110
111         memcpy(mb->wptr, data, size);
112         mb->wptr += size;
113
114         return wrbase;
115 }
116
117 static void mem_file_init(struct mem_file *mf, struct mem_arena *ma)
118 {
119         ZERO_DATA(*mf);
120         mf->ma = ma;
121 }
122
123 static int mem_block_can_write(struct mem_block *mb, size_t size, int flags)
124 {
125         size_t space = mb->top - mb->wptr;
126
127         return (flags & MBWR_SOLID) ? (space >= size) : (space > 0);
128 }
129
130 static void *mem_file_write(struct mem_file *mf, const void *data, size_t size,
131                                                         int flags)
132 {
133         void *wrbase = NULL;
134
135         while (size > 0) {
136                 size_t space, csize;
137                 struct mem_block *mb = mf->tail;
138
139                 if (!mb || !mem_block_can_write(mb, size, flags)) {
140                         mb = mem_block_alloc(mf, max(MEMFILE_BLOCK_SIZE, size));
141                         if (!mf->tail)
142                                 mf->head = mb;
143                         else
144                                 mf->tail->next = mb;
145                         mf->tail = mb;
146                 }
147                 space = mb->top - mb->wptr;
148                 csize = min(size, space);
149
150                 wrbase = mem_block_write(mb, data, csize);
151                 mf->size += csize;
152
153                 size -= csize;
154                 data = (const char *) data + csize;
155         }
156
157         return wrbase;
158 }
159
160 static void mem_file_sync(struct mem_file *mf, FILE *file, uint64_t rel_offset)
161 {
162         struct mem_block *mb;
163
164         if (rel_offset != 0) {
165                 struct mem_file_reloc *rel;
166
167                 always_assert(!mf->relocs || rel_offset != OFFSET_NORELOC);
168
169                 for (rel = mf->relocs; rel; rel = rel->next)
170                         *rel->ptr += rel_offset;
171         }
172
173         for (mb = mf->head; mb; mb = mb->next)
174                 xfwrite(mb->base, mb->wptr - mb->base, file);
175 }
176
177 static struct mem_file_reloc *mem_file_add_reloc(struct mem_file *mf,
178                                                                                                  uint64_t *ptr)
179 {
180         struct mem_file_reloc *rel =
181                 xmem_arena_zalloc(mf->ma, sizeof(struct mem_file_reloc));
182
183         rel->ptr = ptr;
184         rel->next = mf->relocs;
185         mf->relocs = rel;
186
187         return rel;
188 }
189
190 static uint64_t perfconv_make_config_id(bool is_raw, uint64_t type,
191                                                                                 uint64_t event_id)
192 {
193         uint64_t config = is_raw ? (uint64_t) 1 << 63 : 0;
194
195         return config | (type << 56) | (event_id & (((uint64_t) 1 << 56) - 1));
196 }
197
198 static uint64_t perfconv_get_config_type(uint64_t config)
199 {
200         return (config >> 56) & 0x7f;
201 }
202
203 void perfconv_add_kernel_mmap(struct perfconv_context *cctx)
204 {
205         char path[] = "[kernel.kallsyms]";
206         struct static_mmap64 *mm;
207
208         mm = xmem_arena_zalloc(&cctx->ma, sizeof(struct static_mmap64));
209         mm->pid = -1;                           /* Linux HOST_KERNEL_ID == -1 */
210         mm->tid = 0;                            /* Default thread: swapper */
211         mm->header_misc = PERF_RECORD_MISC_KERNEL;
212         /* Linux sets addr = 0, size = 0xffffffff9fffffff, off = 0xffffffff81000000
213          * Their mmap record is also called [kernel.kallsyms]_text (I think).  They
214          * also have a _text symbol in kallsyms at ffffffff81000000 (equiv to our
215          * KERN_LOAD_ADDR (which is 0xffffffffc0000000)).  Either way, this seems to
216          * work for us; we'll see.  It's also arch-independent (for now). */
217         mm->addr = 0;
218         mm->size = 0xffffffffffffffff;
219         mm->offset = 0x0;
220         mm->path = xmem_arena_strdup(&cctx->ma, path);
221
222         mm->next = cctx->static_mmaps;
223         cctx->static_mmaps = mm;
224 }
225
226 static void headers_init(struct perf_headers *hdrs)
227 {
228         ZERO_DATA(*hdrs);
229 }
230
231 static void headers_add_header(struct perf_headers *hdrs, size_t nhdr,
232                                                            struct mem_block *mb)
233 {
234         always_assert(nhdr < HEADER_FEAT_BITS);
235
236         hdrs->headers[nhdr] = mb;
237 }
238
239 static void headers_write(struct perf_headers *hdrs, struct perf_header *ph,
240                                                   struct mem_file *mf)
241 {
242         size_t i;
243
244         for (i = 0; i < HEADER_FEAT_BITS; i++) {
245                 struct mem_block *mb = hdrs->headers[i];
246
247                 if (mb) {
248                         mem_file_write(mf, mb->base, mb->wptr - mb->base, 0);
249                         set_bitno(ph->adds_features, i);
250                 }
251         }
252 }
253
254 static void perf_header_init(struct perf_header *ph)
255 {
256         ZERO_DATA(*ph);
257         ph->magic = PERF_MAGIC2;
258         ph->size = sizeof(*ph);
259         ph->attr_size = sizeof(struct perf_event_attr);
260 }
261
262 static void add_attribute(struct mem_file *amf, struct mem_file *mmf,
263                                                   const struct perf_event_attr *attr,
264                                                   const uint64_t *ids, size_t nids)
265 {
266         struct perf_file_section *psids;
267         struct perf_file_section sids;
268
269         mem_file_write(amf, attr, sizeof(*attr), 0);
270
271         sids.offset = mmf->size;
272         sids.size = nids * sizeof(uint64_t);
273
274         mem_file_write(mmf, ids, nids * sizeof(uint64_t), 0);
275
276         psids = mem_file_write(amf, &sids, sizeof(sids), MBWR_SOLID);
277
278         mem_file_add_reloc(amf, &psids->offset);
279 }
280
281 static void add_default_attribute(struct mem_file *amf, struct mem_file *mmf,
282                                                                   uint64_t config, uint64_t id)
283 {
284         struct perf_event_attr attr;
285
286         ZERO_DATA(attr);
287         attr.type = (uint32_t) perfconv_get_config_type(config);
288         attr.size = sizeof(attr);
289         attr.config = config;
290         attr.mmap = 1;
291         attr.comm = 1;
292         /* We don't know the actual sample_period at this point, but the perf report
293          * percentages are all relative. */
294         attr.sample_period = 1;
295         /* Closely coupled with struct perf_record_sample */
296         attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
297                            PERF_SAMPLE_ADDR | PERF_SAMPLE_ID | PERF_SAMPLE_CPU |
298                            PERF_SAMPLE_CALLCHAIN;
299         add_attribute(amf, mmf, &attr, &id, 1);
300 }
301
302 static uint64_t perfconv_get_event_id(struct perfconv_context *cctx,
303                                                                           uint64_t info)
304 {
305         size_t i, j, n, ipos;
306         uint64_t id, config;
307         struct perf_event_id *nevents;
308
309         for (;;) {
310                 ipos = (size_t) (info % cctx->alloced_events);
311                 for (i = cctx->alloced_events; (i > 0) &&
312                                  (cctx->events[ipos].event != 0);
313                          i--, ipos = (ipos + 1) % cctx->alloced_events) {
314                         if (cctx->events[ipos].event == info)
315                                 return cctx->events[ipos].id;
316                 }
317                 if (i != 0)
318                         break;
319                 /* Need to resize the hash ...
320                  */
321                 n = 2 * cctx->alloced_events;
322                 nevents = xmem_arena_zalloc(&cctx->ma, n * sizeof(*cctx->events));
323                 for (i = 0; i < cctx->alloced_events; i++) {
324                         if (cctx->events[i].event == 0)
325                                 continue;
326                         j = cctx->events[i].event % n;
327                         for (; nevents[j].event; j = (j + 1) % n)
328                                 ;
329                         nevents[j].event = cctx->events[i].event;
330                         nevents[j].id = cctx->events[i].id;
331                 }
332                 cctx->alloced_events = n;
333                 cctx->events = nevents;
334         }
335
336         cctx->events[ipos].event = info;
337         cctx->events[ipos].id = id = cctx->sqnr_id;
338         cctx->sqnr_id++;
339
340         switch ((int) PROF_INFO_DOM(info)) {
341         case PROF_DOM_PMU:
342                 config = perfconv_make_config_id(TRUE, PERF_TYPE_RAW,
343                                                                                  PROF_INFO_DATA(info));
344                 break;
345         case PROF_DOM_TIMER:
346         default:
347                 config = perfconv_make_config_id(FALSE, PERF_TYPE_HARDWARE,
348                                                                                  PERF_COUNT_HW_CPU_CYCLES);
349         }
350
351         add_default_attribute(&cctx->attrs, &cctx->misc, config, id);
352
353         return id;
354 }
355
356 static void emit_static_mmaps(struct perfconv_context *cctx)
357 {
358         struct static_mmap64 *mm;
359
360         for (mm = cctx->static_mmaps; mm; mm = mm->next) {
361                 size_t size = sizeof(struct perf_record_mmap) + strlen(mm->path) + 1;
362                 struct perf_record_mmap *xrec = xzmalloc(size);
363
364                 xrec->header.type = PERF_RECORD_MMAP;
365                 xrec->header.misc = mm->header_misc;
366                 xrec->header.size = size;
367                 xrec->pid = mm->pid;
368                 xrec->tid = mm->tid;
369                 xrec->addr = mm->addr;
370                 xrec->len = mm->size;
371                 xrec->pgoff = mm->offset;
372                 strcpy(xrec->filename, mm->path);
373
374                 mem_file_write(&cctx->data, xrec, size, 0);
375
376                 free(xrec);
377         }
378 }
379
380 static void emit_comm(uint32_t pid, const char *comm,
381                                           struct perfconv_context *cctx)
382 {
383         size_t size = sizeof(struct perf_record_comm) + strlen(comm) + 1;
384         struct perf_record_comm *xrec = xzmalloc(size);
385
386         xrec->header.type = PERF_RECORD_COMM;
387         xrec->header.misc = PERF_RECORD_MISC_USER;
388         xrec->header.size = size;
389         xrec->pid = xrec->tid = pid;
390         strcpy(xrec->comm, comm);
391
392         mem_file_write(&cctx->data, xrec, size, 0);
393
394         free(xrec);
395 }
396
397 static void emit_pid_mmap64(struct perf_record *pr,
398                                                         struct perfconv_context *cctx)
399 {
400         struct proftype_pid_mmap64 *rec = (struct proftype_pid_mmap64 *) pr->data;
401         size_t size = sizeof(struct perf_record_mmap) + strlen(rec->path) + 1;
402         struct perf_record_mmap *xrec = xzmalloc(size);
403
404         xrec->header.type = PERF_RECORD_MMAP;
405         xrec->header.misc = PERF_RECORD_MISC_USER;
406         xrec->header.size = size;
407         xrec->pid = xrec->tid = rec->pid;
408         xrec->addr = rec->addr;
409         xrec->len = rec->size;
410         xrec->pgoff = rec->offset;
411         strcpy(xrec->filename, rec->path);
412
413         mem_file_write(&cctx->data, xrec, size, 0);
414
415         free(xrec);
416 }
417
418 static void emit_kernel_trace64(struct perf_record *pr,
419                                                                 struct perfconv_context *cctx)
420 {
421         struct proftype_kern_trace64 *rec = (struct proftype_kern_trace64 *)
422                 pr->data;
423         size_t size = sizeof(struct perf_record_sample) +
424                 (rec->num_traces - 1) * sizeof(uint64_t);
425         struct perf_record_sample *xrec = xzmalloc(size);
426
427         xrec->header.type = PERF_RECORD_SAMPLE;
428         xrec->header.misc = PERF_RECORD_MISC_KERNEL;
429         xrec->header.size = size;
430         xrec->ip = rec->trace[0];
431         /* TODO: We could have pid/tid for kernel tasks during their lifetime.
432          * During syscalls, we could use the pid of the process.  For the kernel
433          * itself, -1 seems to be generic kernel stuff, and tid == 0 is 'swapper'.
434          *
435          * Right now, the kernel doesn't even tell us the pid, so we have no way of
436          * knowing from userspace. */
437         xrec->pid = -1;
438         xrec->tid = 0;
439         xrec->time = rec->tstamp;
440         xrec->addr = rec->trace[0];
441         xrec->id = perfconv_get_event_id(cctx, rec->info);
442         xrec->cpu = rec->cpu;
443         xrec->nr = rec->num_traces - 1;
444         memcpy(xrec->ips, rec->trace + 1, (rec->num_traces - 1) * sizeof(uint64_t));
445
446         mem_file_write(&cctx->data, xrec, size, 0);
447
448         free(xrec);
449 }
450
451 static void emit_user_trace64(struct perf_record *pr,
452                                                           struct perfconv_context *cctx)
453 {
454         struct proftype_user_trace64 *rec = (struct proftype_user_trace64 *)
455                 pr->data;
456         size_t size = sizeof(struct perf_record_sample) +
457                 (rec->num_traces - 1) * sizeof(uint64_t);
458         struct perf_record_sample *xrec = xzmalloc(size);
459
460         xrec->header.type = PERF_RECORD_SAMPLE;
461         xrec->header.misc = PERF_RECORD_MISC_USER;
462         xrec->header.size = size;
463         xrec->ip = rec->trace[0];
464         xrec->pid = xrec->tid = rec->pid;
465         xrec->time = rec->tstamp;
466         xrec->addr = rec->trace[0];
467         xrec->id = perfconv_get_event_id(cctx, rec->info);
468         xrec->cpu = rec->cpu;
469         xrec->nr = rec->num_traces - 1;
470         memcpy(xrec->ips, rec->trace + 1, (rec->num_traces - 1) * sizeof(uint64_t));
471
472         mem_file_write(&cctx->data, xrec, size, 0);
473
474         free(xrec);
475 }
476
477 static void emit_new_process(struct perf_record *pr,
478                                                          struct perfconv_context *cctx)
479 {
480         struct proftype_new_process *rec = (struct proftype_new_process *) pr->data;
481         const char *comm = strrchr(rec->path, '/');
482
483         if (!comm)
484                 comm = rec->path;
485         else
486                 comm++;
487         emit_comm(rec->pid, comm, cctx);
488 }
489
490 static void add_event_type(struct mem_file *mf, uint64_t id, const char *name)
491 {
492         struct perf_trace_event_type evt;
493
494         ZERO_DATA(evt);
495         evt.event_id = id;
496         strncpy(evt.name, name, sizeof(evt.name));
497         evt.name[sizeof(evt.name) - 1] = 0;
498
499         mem_file_write(mf, &evt, sizeof(evt), 0);
500 }
501
502 struct perfconv_context *perfconv_create_context(struct perf_context *pctx)
503 {
504         struct perfconv_context *cctx = xzmalloc(sizeof(struct perfconv_context));
505
506         cctx->pctx = pctx;
507         xmem_arena_init(&cctx->ma, 0);
508         cctx->alloced_events = 128;
509         cctx->events = xmem_arena_zalloc(
510                 &cctx->ma, cctx->alloced_events * sizeof(*cctx->events));
511         perf_header_init(&cctx->ph);
512         headers_init(&cctx->hdrs);
513         mem_file_init(&cctx->fhdrs, &cctx->ma);
514         mem_file_init(&cctx->misc, &cctx->ma);
515         mem_file_init(&cctx->attrs, &cctx->ma);
516         mem_file_init(&cctx->data, &cctx->ma);
517         mem_file_init(&cctx->event_types, &cctx->ma);
518
519         return cctx;
520 }
521
522 void perfconv_free_context(struct perfconv_context *cctx)
523 {
524         if (cctx) {
525                 xmem_arena_destroy(&cctx->ma);
526                 free(cctx);
527         }
528 }
529
530 void perfconv_process_input(struct perfconv_context *cctx, FILE *input,
531                                                         FILE *output)
532 {
533         size_t processed_records = 0;
534         uint64_t offset, rel_offset;
535         struct perf_record pr;
536
537         emit_static_mmaps(cctx);
538
539         while (read_record(input, &pr) == 0) {
540                 dbg_print(cctx, 8, stderr, "Valid record: type=%lu size=%lu\n",
541                                   pr.type, pr.size);
542
543                 processed_records++;
544
545                 switch (pr.type) {
546                 case PROFTYPE_KERN_TRACE64:
547                         emit_kernel_trace64(&pr, cctx);
548                         break;
549                 case PROFTYPE_USER_TRACE64:
550                         emit_user_trace64(&pr, cctx);
551                         break;
552                 case PROFTYPE_PID_MMAP64:
553                         emit_pid_mmap64(&pr, cctx);
554                         break;
555                 case PROFTYPE_NEW_PROCESS:
556                         emit_new_process(&pr, cctx);
557                         break;
558                 default:
559                         fprintf(stderr, "Unknown record: type=%lu size=%lu\n", pr.type,
560                                         pr.size);
561                         processed_records--;
562                 }
563
564                 free_record(&pr);
565         }
566
567         headers_write(&cctx->hdrs, &cctx->ph, &cctx->fhdrs);
568         offset = sizeof(cctx->ph) + cctx->fhdrs.size + cctx->misc.size;
569
570         if (cctx->event_types.size > 0) {
571                 cctx->ph.event_types.offset = offset;
572                 cctx->ph.event_types.size = cctx->event_types.size;
573                 offset += cctx->event_types.size;
574         }
575         if (cctx->attrs.size > 0) {
576                 cctx->ph.attrs.offset = offset;
577                 cctx->ph.attrs.size = cctx->attrs.size;
578                 offset += cctx->attrs.size;
579         }
580         if (cctx->data.size > 0) {
581                 cctx->ph.data.offset = offset;
582                 cctx->ph.data.size = cctx->data.size;
583                 offset += cctx->data.size;
584         }
585
586         xfwrite(&cctx->ph, sizeof(cctx->ph), output);
587         mem_file_sync(&cctx->fhdrs, output, OFFSET_NORELOC);
588
589         rel_offset = (uint64_t) ftell(output);
590         mem_file_sync(&cctx->misc, output, rel_offset);
591
592         mem_file_sync(&cctx->event_types, output, rel_offset);
593         mem_file_sync(&cctx->attrs, output, rel_offset);
594         mem_file_sync(&cctx->data, output, rel_offset);
595
596         dbg_print(cctx, 2, stderr, "Conversion succeeded: %lu records converted\n",
597                           processed_records);
598 }