perf: Clean up attr production
[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 void perfconv_add_kernel_mmap(struct perfconv_context *cctx)
191 {
192         char path[] = "[kernel.kallsyms]";
193         struct static_mmap64 *mm;
194
195         mm = xmem_arena_zalloc(&cctx->ma, sizeof(struct static_mmap64));
196         mm->pid = -1;                           /* Linux HOST_KERNEL_ID == -1 */
197         mm->tid = 0;                            /* Default thread: swapper */
198         mm->header_misc = PERF_RECORD_MISC_KERNEL;
199         /* Linux sets addr = 0, size = 0xffffffff9fffffff, off = 0xffffffff81000000
200          * Their mmap record is also called [kernel.kallsyms]_text (I think).  They
201          * also have a _text symbol in kallsyms at ffffffff81000000 (equiv to our
202          * KERN_LOAD_ADDR (which is 0xffffffffc0000000)).  Either way, this seems to
203          * work for us; we'll see.  It's also arch-independent (for now). */
204         mm->addr = 0;
205         mm->size = 0xffffffffffffffff;
206         mm->offset = 0x0;
207         mm->path = xmem_arena_strdup(&cctx->ma, path);
208
209         mm->next = cctx->static_mmaps;
210         cctx->static_mmaps = mm;
211 }
212
213 static void headers_init(struct perf_headers *hdrs)
214 {
215         ZERO_DATA(*hdrs);
216 }
217
218 static void headers_add_header(struct perf_headers *hdrs, size_t nhdr,
219                                                            struct mem_block *mb)
220 {
221         always_assert(nhdr < HEADER_FEAT_BITS);
222
223         hdrs->headers[nhdr] = mb;
224 }
225
226 static void headers_write(struct perf_headers *hdrs, struct perf_header *ph,
227                                                   struct mem_file *mf)
228 {
229         size_t i;
230
231         for (i = 0; i < HEADER_FEAT_BITS; i++) {
232                 struct mem_block *mb = hdrs->headers[i];
233
234                 if (mb) {
235                         mem_file_write(mf, mb->base, mb->wptr - mb->base, 0);
236                         set_bitno(ph->adds_features, i);
237                 }
238         }
239 }
240
241 static void perf_header_init(struct perf_header *ph)
242 {
243         ZERO_DATA(*ph);
244         ph->magic = PERF_MAGIC2;
245         ph->size = sizeof(*ph);
246         ph->attr_size = sizeof(struct perf_event_attr);
247 }
248
249 static void emit_attr(struct mem_file *amf, struct mem_file *mmf,
250                       const struct perf_event_attr *attr, uint64_t id)
251 {
252         struct perf_file_section *psids;
253         struct perf_file_section sids;
254
255         mem_file_write(amf, attr, sizeof(*attr), 0);
256
257         sids.offset = mmf->size;
258         sids.size = sizeof(uint64_t);
259
260         mem_file_write(mmf, &id, sizeof(uint64_t), 0);
261
262         psids = mem_file_write(amf, &sids, sizeof(sids), MBWR_SOLID);
263
264         mem_file_add_reloc(amf, &psids->offset);
265 }
266
267 /* Given raw_info, which is what the kernel sends as user_data for a particular
268  * sample, look up the 'id' for the event/sample.  The 'id' identifies the event
269  * stream that the sample is a part of.  There are many samples per event
270  * stream, all identified by 'id.'  It doesn't matter what 'id', so long as it
271  * is unique.  We happen to use the pointer to the sample's eventsel.
272  *
273  * If this is the first time we've seen 'raw_info', we'll also add an attribute
274  * to the perf ctx.  There is one attr per 'id' / event stream. */
275 static uint64_t perfconv_get_event_id(struct perfconv_context *cctx,
276                                                                           uint64_t raw_info)
277 {
278         struct perf_eventsel *sel = (struct perf_eventsel*)raw_info;
279         struct perf_event_attr attr;
280         uint64_t raw_event;
281
282         assert(sel);
283         if (sel->attr_emitted)
284                 return raw_info;
285         raw_event = sel->ev.event;
286         ZERO_DATA(attr);
287         attr.size = sizeof(attr);
288         attr.mmap = 1;
289         attr.comm = 1;
290         attr.sample_period = sel->ev.trigger_count;
291         /* Closely coupled with struct perf_record_sample */
292         attr.sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
293                            PERF_SAMPLE_ADDR | PERF_SAMPLE_ID | PERF_SAMPLE_CPU |
294                            PERF_SAMPLE_CALLCHAIN;
295         attr.exclude_guest = 1; /* we can't trace VMs yet */
296         attr.exclude_hv = 1;    /* we aren't tracing our hypervisor, AFAIK */
297         attr.exclude_user = !PMEV_GET_USR(raw_event);
298         attr.exclude_kernel = !PMEV_GET_OS(raw_event);
299         attr.type = sel->type;
300         attr.config = sel->config;
301         emit_attr(&cctx->attrs, &cctx->misc, &attr, raw_info);
302         sel->attr_emitted = TRUE;
303         return raw_info;
304 }
305
306 static void emit_static_mmaps(struct perfconv_context *cctx)
307 {
308         struct static_mmap64 *mm;
309
310         for (mm = cctx->static_mmaps; mm; mm = mm->next) {
311                 size_t size = sizeof(struct perf_record_mmap) + strlen(mm->path) + 1;
312                 struct perf_record_mmap *xrec = xzmalloc(size);
313
314                 xrec->header.type = PERF_RECORD_MMAP;
315                 xrec->header.misc = mm->header_misc;
316                 xrec->header.size = size;
317                 xrec->pid = mm->pid;
318                 xrec->tid = mm->tid;
319                 xrec->addr = mm->addr;
320                 xrec->len = mm->size;
321                 xrec->pgoff = mm->offset;
322                 strcpy(xrec->filename, mm->path);
323
324                 mem_file_write(&cctx->data, xrec, size, 0);
325
326                 free(xrec);
327         }
328 }
329
330 static void emit_comm(uint32_t pid, const char *comm,
331                                           struct perfconv_context *cctx)
332 {
333         size_t size = sizeof(struct perf_record_comm) + strlen(comm) + 1;
334         struct perf_record_comm *xrec = xzmalloc(size);
335
336         xrec->header.type = PERF_RECORD_COMM;
337         xrec->header.misc = PERF_RECORD_MISC_USER;
338         xrec->header.size = size;
339         xrec->pid = xrec->tid = pid;
340         strcpy(xrec->comm, comm);
341
342         mem_file_write(&cctx->data, xrec, size, 0);
343
344         free(xrec);
345 }
346
347 static void emit_pid_mmap64(struct perf_record *pr,
348                                                         struct perfconv_context *cctx)
349 {
350         struct proftype_pid_mmap64 *rec = (struct proftype_pid_mmap64 *) pr->data;
351         size_t size = sizeof(struct perf_record_mmap) + strlen(rec->path) + 1;
352         struct perf_record_mmap *xrec = xzmalloc(size);
353
354         xrec->header.type = PERF_RECORD_MMAP;
355         xrec->header.misc = PERF_RECORD_MISC_USER;
356         xrec->header.size = size;
357         xrec->pid = xrec->tid = rec->pid;
358         xrec->addr = rec->addr;
359         xrec->len = rec->size;
360         xrec->pgoff = rec->offset;
361         strcpy(xrec->filename, rec->path);
362
363         mem_file_write(&cctx->data, xrec, size, 0);
364
365         free(xrec);
366 }
367
368 static void emit_kernel_trace64(struct perf_record *pr,
369                                                                 struct perfconv_context *cctx)
370 {
371         struct proftype_kern_trace64 *rec = (struct proftype_kern_trace64 *)
372                 pr->data;
373         size_t size = sizeof(struct perf_record_sample) +
374                 (rec->num_traces - 1) * sizeof(uint64_t);
375         struct perf_record_sample *xrec = xzmalloc(size);
376
377         xrec->header.type = PERF_RECORD_SAMPLE;
378         xrec->header.misc = PERF_RECORD_MISC_KERNEL;
379         xrec->header.size = size;
380         xrec->ip = rec->trace[0];
381         /* TODO: We could have pid/tid for kernel tasks during their lifetime.
382          * During syscalls, we could use the pid of the process.  For the kernel
383          * itself, -1 seems to be generic kernel stuff, and tid == 0 is 'swapper'.
384          *
385          * Right now, the kernel doesn't even tell us the pid, so we have no way of
386          * knowing from userspace. */
387         xrec->pid = -1;
388         xrec->tid = 0;
389         xrec->time = rec->tstamp;
390         xrec->addr = rec->trace[0];
391         xrec->id = perfconv_get_event_id(cctx, rec->info);
392         xrec->cpu = rec->cpu;
393         xrec->nr = rec->num_traces - 1;
394         memcpy(xrec->ips, rec->trace + 1, (rec->num_traces - 1) * sizeof(uint64_t));
395
396         mem_file_write(&cctx->data, xrec, size, 0);
397
398         free(xrec);
399 }
400
401 static void emit_user_trace64(struct perf_record *pr,
402                                                           struct perfconv_context *cctx)
403 {
404         struct proftype_user_trace64 *rec = (struct proftype_user_trace64 *)
405                 pr->data;
406         size_t size = sizeof(struct perf_record_sample) +
407                 (rec->num_traces - 1) * sizeof(uint64_t);
408         struct perf_record_sample *xrec = xzmalloc(size);
409
410         xrec->header.type = PERF_RECORD_SAMPLE;
411         xrec->header.misc = PERF_RECORD_MISC_USER;
412         xrec->header.size = size;
413         xrec->ip = rec->trace[0];
414         xrec->pid = xrec->tid = rec->pid;
415         xrec->time = rec->tstamp;
416         xrec->addr = rec->trace[0];
417         xrec->id = perfconv_get_event_id(cctx, rec->info);
418         xrec->cpu = rec->cpu;
419         xrec->nr = rec->num_traces - 1;
420         memcpy(xrec->ips, rec->trace + 1, (rec->num_traces - 1) * sizeof(uint64_t));
421
422         mem_file_write(&cctx->data, xrec, size, 0);
423
424         free(xrec);
425 }
426
427 static void emit_new_process(struct perf_record *pr,
428                                                          struct perfconv_context *cctx)
429 {
430         struct proftype_new_process *rec = (struct proftype_new_process *) pr->data;
431         const char *comm = strrchr(rec->path, '/');
432
433         if (!comm)
434                 comm = rec->path;
435         else
436                 comm++;
437         emit_comm(rec->pid, comm, cctx);
438 }
439
440 static void add_event_type(struct mem_file *mf, uint64_t id, const char *name)
441 {
442         struct perf_trace_event_type evt;
443
444         ZERO_DATA(evt);
445         evt.event_id = id;
446         strncpy(evt.name, name, sizeof(evt.name));
447         evt.name[sizeof(evt.name) - 1] = 0;
448
449         mem_file_write(mf, &evt, sizeof(evt), 0);
450 }
451
452 struct perfconv_context *perfconv_create_context(struct perf_context *pctx)
453 {
454         struct perfconv_context *cctx = xzmalloc(sizeof(struct perfconv_context));
455
456         cctx->pctx = pctx;
457         xmem_arena_init(&cctx->ma, 0);
458         perf_header_init(&cctx->ph);
459         headers_init(&cctx->hdrs);
460         mem_file_init(&cctx->fhdrs, &cctx->ma);
461         mem_file_init(&cctx->misc, &cctx->ma);
462         mem_file_init(&cctx->attrs, &cctx->ma);
463         mem_file_init(&cctx->data, &cctx->ma);
464         mem_file_init(&cctx->event_types, &cctx->ma);
465
466         return cctx;
467 }
468
469 void perfconv_free_context(struct perfconv_context *cctx)
470 {
471         if (cctx) {
472                 xmem_arena_destroy(&cctx->ma);
473                 free(cctx);
474         }
475 }
476
477 void perfconv_process_input(struct perfconv_context *cctx, FILE *input,
478                                                         FILE *output)
479 {
480         size_t processed_records = 0;
481         uint64_t offset, rel_offset;
482         struct perf_record pr;
483
484         emit_static_mmaps(cctx);
485
486         while (read_record(input, &pr) == 0) {
487                 dbg_print(cctx, 8, stderr, "Valid record: type=%lu size=%lu\n",
488                                   pr.type, pr.size);
489
490                 processed_records++;
491
492                 switch (pr.type) {
493                 case PROFTYPE_KERN_TRACE64:
494                         emit_kernel_trace64(&pr, cctx);
495                         break;
496                 case PROFTYPE_USER_TRACE64:
497                         emit_user_trace64(&pr, cctx);
498                         break;
499                 case PROFTYPE_PID_MMAP64:
500                         emit_pid_mmap64(&pr, cctx);
501                         break;
502                 case PROFTYPE_NEW_PROCESS:
503                         emit_new_process(&pr, cctx);
504                         break;
505                 default:
506                         fprintf(stderr, "Unknown record: type=%lu size=%lu\n", pr.type,
507                                         pr.size);
508                         processed_records--;
509                 }
510
511                 free_record(&pr);
512         }
513
514         headers_write(&cctx->hdrs, &cctx->ph, &cctx->fhdrs);
515         offset = sizeof(cctx->ph) + cctx->fhdrs.size + cctx->misc.size;
516
517         if (cctx->event_types.size > 0) {
518                 cctx->ph.event_types.offset = offset;
519                 cctx->ph.event_types.size = cctx->event_types.size;
520                 offset += cctx->event_types.size;
521         }
522         if (cctx->attrs.size > 0) {
523                 cctx->ph.attrs.offset = offset;
524                 cctx->ph.attrs.size = cctx->attrs.size;
525                 offset += cctx->attrs.size;
526         }
527         if (cctx->data.size > 0) {
528                 cctx->ph.data.offset = offset;
529                 cctx->ph.data.size = cctx->data.size;
530                 offset += cctx->data.size;
531         }
532
533         xfwrite(&cctx->ph, sizeof(cctx->ph), output);
534         mem_file_sync(&cctx->fhdrs, output, OFFSET_NORELOC);
535
536         rel_offset = (uint64_t) ftell(output);
537         mem_file_sync(&cctx->misc, output, rel_offset);
538
539         mem_file_sync(&cctx->event_types, output, rel_offset);
540         mem_file_sync(&cctx->attrs, output, rel_offset);
541         mem_file_sync(&cctx->data, output, rel_offset);
542
543         dbg_print(cctx, 2, stderr, "Conversion succeeded: %lu records converted\n",
544                           processed_records);
545 }