Commit | Line | Data |
---|---|---|
80eeb67f AK |
1 | #define _XOPEN_SOURCE 500 /* needed for nftw() */ |
2 | #define _GNU_SOURCE /* needed for asprintf() */ | |
3 | ||
4 | /* Parse event JSON files */ | |
5 | ||
6 | /* | |
7 | * Copyright (c) 2014, Intel Corporation | |
8 | * All rights reserved. | |
9 | * | |
10 | * Redistribution and use in source and binary forms, with or without | |
11 | * modification, are permitted provided that the following conditions are met: | |
12 | * | |
13 | * 1. Redistributions of source code must retain the above copyright notice, | |
14 | * this list of conditions and the following disclaimer. | |
15 | * | |
16 | * 2. Redistributions in binary form must reproduce the above copyright | |
17 | * notice, this list of conditions and the following disclaimer in the | |
18 | * documentation and/or other materials provided with the distribution. | |
19 | * | |
20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
23 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE | |
24 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |
25 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
26 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
27 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
28 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |
29 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
30 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |
31 | * OF THE POSSIBILITY OF SUCH DAMAGE. | |
32 | */ | |
33 | ||
34 | #include <stdio.h> | |
35 | #include <stdlib.h> | |
36 | #include <errno.h> | |
37 | #include <string.h> | |
38 | #include <ctype.h> | |
39 | #include <unistd.h> | |
40 | #include <stdarg.h> | |
41 | #include <libgen.h> | |
51ce1dcc | 42 | #include <limits.h> |
80eeb67f AK |
43 | #include <dirent.h> |
44 | #include <sys/time.h> /* getrlimit */ | |
45 | #include <sys/resource.h> /* getrlimit */ | |
46 | #include <ftw.h> | |
47 | #include <sys/stat.h> | |
e9d32c1b | 48 | #include <linux/list.h> |
80eeb67f AK |
49 | #include "jsmn.h" |
50 | #include "json.h" | |
51 | #include "jevents.h" | |
52 | ||
80eeb67f AK |
53 | int verbose; |
54 | char *prog; | |
55 | ||
56 | int eprintf(int level, int var, const char *fmt, ...) | |
57 | { | |
58 | ||
59 | int ret; | |
60 | va_list args; | |
61 | ||
62 | if (var < level) | |
63 | return 0; | |
64 | ||
65 | va_start(args, fmt); | |
66 | ||
67 | ret = vfprintf(stderr, fmt, args); | |
68 | ||
69 | va_end(args); | |
70 | ||
71 | return ret; | |
72 | } | |
73 | ||
74 | __attribute__((weak)) char *get_cpu_str(void) | |
75 | { | |
76 | return NULL; | |
77 | } | |
78 | ||
79 | static void addfield(char *map, char **dst, const char *sep, | |
80 | const char *a, jsmntok_t *bt) | |
81 | { | |
82 | unsigned int len = strlen(a) + 1 + strlen(sep); | |
83 | int olen = *dst ? strlen(*dst) : 0; | |
84 | int blen = bt ? json_len(bt) : 0; | |
85 | char *out; | |
86 | ||
87 | out = realloc(*dst, len + olen + blen); | |
88 | if (!out) { | |
89 | /* Don't add field in this case */ | |
90 | return; | |
91 | } | |
92 | *dst = out; | |
93 | ||
94 | if (!olen) | |
95 | *(*dst) = 0; | |
96 | else | |
97 | strcat(*dst, sep); | |
98 | strcat(*dst, a); | |
99 | if (bt) | |
100 | strncat(*dst, map + bt->start, blen); | |
101 | } | |
102 | ||
103 | static void fixname(char *s) | |
104 | { | |
105 | for (; *s; s++) | |
106 | *s = tolower(*s); | |
107 | } | |
108 | ||
109 | static void fixdesc(char *s) | |
110 | { | |
111 | char *e = s + strlen(s); | |
112 | ||
113 | /* Remove trailing dots that look ugly in perf list */ | |
114 | --e; | |
115 | while (e >= s && isspace(*e)) | |
116 | --e; | |
117 | if (*e == '.') | |
118 | *e = 0; | |
119 | } | |
120 | ||
fbc2844e WC |
121 | /* Add escapes for '\' so they are proper C strings. */ |
122 | static char *fixregex(char *s) | |
123 | { | |
124 | int len = 0; | |
125 | int esc_count = 0; | |
126 | char *fixed = NULL; | |
127 | char *p, *q; | |
128 | ||
129 | /* Count the number of '\' in string */ | |
130 | for (p = s; *p; p++) { | |
131 | ++len; | |
132 | if (*p == '\\') | |
133 | ++esc_count; | |
134 | } | |
135 | ||
136 | if (esc_count == 0) | |
137 | return s; | |
138 | ||
139 | /* allocate space for a new string */ | |
140 | fixed = (char *) malloc(len + 1); | |
141 | if (!fixed) | |
142 | return NULL; | |
143 | ||
144 | /* copy over the characters */ | |
145 | q = fixed; | |
146 | for (p = s; *p; p++) { | |
147 | if (*p == '\\') { | |
148 | *q = '\\'; | |
149 | ++q; | |
150 | } | |
151 | *q = *p; | |
152 | ++q; | |
153 | } | |
154 | *q = '\0'; | |
155 | return fixed; | |
156 | } | |
157 | ||
80eeb67f AK |
158 | static struct msrmap { |
159 | const char *num; | |
160 | const char *pname; | |
161 | } msrmap[] = { | |
162 | { "0x3F6", "ldlat=" }, | |
163 | { "0x1A6", "offcore_rsp=" }, | |
164 | { "0x1A7", "offcore_rsp=" }, | |
b42c7369 | 165 | { "0x3F7", "frontend=" }, |
80eeb67f AK |
166 | { NULL, NULL } |
167 | }; | |
168 | ||
169 | static struct field { | |
170 | const char *field; | |
171 | const char *kernel; | |
172 | } fields[] = { | |
80eeb67f AK |
173 | { "UMask", "umask=" }, |
174 | { "CounterMask", "cmask=" }, | |
175 | { "Invert", "inv=" }, | |
176 | { "AnyThread", "any=" }, | |
177 | { "EdgeDetect", "edge=" }, | |
178 | { "SampleAfterValue", "period=" }, | |
c73881ee AK |
179 | { "FCMask", "fc_mask=" }, |
180 | { "PortMask", "ch_mask=" }, | |
80eeb67f AK |
181 | { NULL, NULL } |
182 | }; | |
183 | ||
184 | static void cut_comma(char *map, jsmntok_t *newval) | |
185 | { | |
186 | int i; | |
187 | ||
188 | /* Cut off everything after comma */ | |
189 | for (i = newval->start; i < newval->end; i++) { | |
190 | if (map[i] == ',') | |
191 | newval->end = i; | |
192 | } | |
193 | } | |
194 | ||
195 | static int match_field(char *map, jsmntok_t *field, int nz, | |
196 | char **event, jsmntok_t *val) | |
197 | { | |
198 | struct field *f; | |
199 | jsmntok_t newval = *val; | |
200 | ||
201 | for (f = fields; f->field; f++) | |
202 | if (json_streq(map, field, f->field) && nz) { | |
203 | cut_comma(map, &newval); | |
204 | addfield(map, event, ",", f->kernel, &newval); | |
205 | return 1; | |
206 | } | |
207 | return 0; | |
208 | } | |
209 | ||
210 | static struct msrmap *lookup_msr(char *map, jsmntok_t *val) | |
211 | { | |
212 | jsmntok_t newval = *val; | |
213 | static bool warned; | |
214 | int i; | |
215 | ||
216 | cut_comma(map, &newval); | |
217 | for (i = 0; msrmap[i].num; i++) | |
218 | if (json_streq(map, &newval, msrmap[i].num)) | |
219 | return &msrmap[i]; | |
220 | if (!warned) { | |
221 | warned = true; | |
222 | pr_err("%s: Unknown MSR in event file %.*s\n", prog, | |
223 | json_len(val), map + val->start); | |
224 | } | |
225 | return NULL; | |
226 | } | |
227 | ||
fedb2b51 AK |
228 | static struct map { |
229 | const char *json; | |
230 | const char *perf; | |
231 | } unit_to_pmu[] = { | |
232 | { "CBO", "uncore_cbox" }, | |
233 | { "QPI LL", "uncore_qpi" }, | |
234 | { "SBO", "uncore_sbox" }, | |
af34cb4f | 235 | { "iMPH-U", "uncore_arb" }, |
9bacbced TR |
236 | { "CPU-M-CF", "cpum_cf" }, |
237 | { "CPU-M-SF", "cpum_sf" }, | |
fedb2b51 AK |
238 | {} |
239 | }; | |
240 | ||
241 | static const char *field_to_perf(struct map *table, char *map, jsmntok_t *val) | |
242 | { | |
243 | int i; | |
244 | ||
245 | for (i = 0; table[i].json; i++) { | |
246 | if (json_streq(map, val, table[i].json)) | |
247 | return table[i].perf; | |
248 | } | |
249 | return NULL; | |
250 | } | |
251 | ||
80eeb67f AK |
252 | #define EXPECT(e, t, m) do { if (!(e)) { \ |
253 | jsmntok_t *loc = (t); \ | |
254 | if (!(t)->start && (t) > tokens) \ | |
255 | loc = (t) - 1; \ | |
931ef5dc JG |
256 | pr_err("%s:%d: " m ", got %s\n", fn, \ |
257 | json_line(map, loc), \ | |
258 | json_name(t)); \ | |
259 | err = -EIO; \ | |
80eeb67f AK |
260 | goto out_free; \ |
261 | } } while (0) | |
262 | ||
6f2f2ca3 | 263 | static char *topic; |
80eeb67f AK |
264 | |
265 | static char *get_topic(void) | |
266 | { | |
6f2f2ca3 | 267 | char *tp; |
80eeb67f AK |
268 | int i; |
269 | ||
6f2f2ca3 JG |
270 | /* tp is free'd in process_one_file() */ |
271 | i = asprintf(&tp, "%s", topic); | |
272 | if (i < 0) { | |
273 | pr_info("%s: asprintf() error %s\n", prog); | |
274 | return NULL; | |
80eeb67f AK |
275 | } |
276 | ||
277 | for (i = 0; i < (int) strlen(tp); i++) { | |
278 | char c = tp[i]; | |
279 | ||
280 | if (c == '-') | |
281 | tp[i] = ' '; | |
282 | else if (c == '.') { | |
283 | tp[i] = '\0'; | |
284 | break; | |
285 | } | |
286 | } | |
287 | ||
288 | return tp; | |
289 | } | |
290 | ||
6f2f2ca3 | 291 | static int add_topic(char *bname) |
80eeb67f | 292 | { |
6f2f2ca3 | 293 | free(topic); |
80eeb67f AK |
294 | topic = strdup(bname); |
295 | if (!topic) { | |
296 | pr_info("%s: strdup() error %s for file %s\n", prog, | |
297 | strerror(errno), bname); | |
298 | return -ENOMEM; | |
299 | } | |
80eeb67f AK |
300 | return 0; |
301 | } | |
302 | ||
303 | struct perf_entry_data { | |
304 | FILE *outfp; | |
305 | char *topic; | |
306 | }; | |
307 | ||
308 | static int close_table; | |
309 | ||
310 | static void print_events_table_prefix(FILE *fp, const char *tblname) | |
311 | { | |
312 | fprintf(fp, "struct pmu_event %s[] = {\n", tblname); | |
313 | close_table = 1; | |
314 | } | |
315 | ||
316 | static int print_events_table_entry(void *data, char *name, char *event, | |
fedb2b51 | 317 | char *desc, char *long_desc, |
00636c3b | 318 | char *pmu, char *unit, char *perpkg, |
96284814 | 319 | char *metric_expr, |
3ba36d36 | 320 | char *metric_name, char *metric_group) |
80eeb67f AK |
321 | { |
322 | struct perf_entry_data *pd = data; | |
323 | FILE *outfp = pd->outfp; | |
324 | char *topic = pd->topic; | |
325 | ||
326 | /* | |
327 | * TODO: Remove formatting chars after debugging to reduce | |
328 | * string lengths. | |
329 | */ | |
330 | fprintf(outfp, "{\n"); | |
331 | ||
3ba36d36 AK |
332 | if (name) |
333 | fprintf(outfp, "\t.name = \"%s\",\n", name); | |
334 | if (event) | |
335 | fprintf(outfp, "\t.event = \"%s\",\n", event); | |
80eeb67f AK |
336 | fprintf(outfp, "\t.desc = \"%s\",\n", desc); |
337 | fprintf(outfp, "\t.topic = \"%s\",\n", topic); | |
794ba54a SB |
338 | if (long_desc && long_desc[0]) |
339 | fprintf(outfp, "\t.long_desc = \"%s\",\n", long_desc); | |
fedb2b51 AK |
340 | if (pmu) |
341 | fprintf(outfp, "\t.pmu = \"%s\",\n", pmu); | |
342 | if (unit) | |
343 | fprintf(outfp, "\t.unit = \"%s\",\n", unit); | |
344 | if (perpkg) | |
345 | fprintf(outfp, "\t.perpkg = \"%s\",\n", perpkg); | |
00636c3b AK |
346 | if (metric_expr) |
347 | fprintf(outfp, "\t.metric_expr = \"%s\",\n", metric_expr); | |
96284814 AK |
348 | if (metric_name) |
349 | fprintf(outfp, "\t.metric_name = \"%s\",\n", metric_name); | |
3ba36d36 AK |
350 | if (metric_group) |
351 | fprintf(outfp, "\t.metric_group = \"%s\",\n", metric_group); | |
80eeb67f AK |
352 | fprintf(outfp, "},\n"); |
353 | ||
354 | return 0; | |
355 | } | |
356 | ||
e9d32c1b JG |
357 | struct event_struct { |
358 | struct list_head list; | |
359 | char *name; | |
360 | char *event; | |
361 | char *desc; | |
362 | char *long_desc; | |
363 | char *pmu; | |
364 | char *unit; | |
365 | char *perpkg; | |
366 | char *metric_expr; | |
367 | char *metric_name; | |
368 | char *metric_group; | |
369 | }; | |
370 | ||
371 | #define ADD_EVENT_FIELD(field) do { if (field) { \ | |
372 | es->field = strdup(field); \ | |
373 | if (!es->field) \ | |
374 | goto out_free; \ | |
375 | } } while (0) | |
376 | ||
377 | #define FREE_EVENT_FIELD(field) free(es->field) | |
378 | ||
379 | #define TRY_FIXUP_FIELD(field) do { if (es->field && !*field) {\ | |
380 | *field = strdup(es->field); \ | |
381 | if (!*field) \ | |
382 | return -ENOMEM; \ | |
383 | } } while (0) | |
384 | ||
385 | #define FOR_ALL_EVENT_STRUCT_FIELDS(op) do { \ | |
386 | op(name); \ | |
387 | op(event); \ | |
388 | op(desc); \ | |
389 | op(long_desc); \ | |
390 | op(pmu); \ | |
391 | op(unit); \ | |
392 | op(perpkg); \ | |
393 | op(metric_expr); \ | |
394 | op(metric_name); \ | |
395 | op(metric_group); \ | |
396 | } while (0) | |
397 | ||
398 | static LIST_HEAD(arch_std_events); | |
399 | ||
400 | static void free_arch_std_events(void) | |
401 | { | |
402 | struct event_struct *es, *next; | |
403 | ||
404 | list_for_each_entry_safe(es, next, &arch_std_events, list) { | |
405 | FOR_ALL_EVENT_STRUCT_FIELDS(FREE_EVENT_FIELD); | |
406 | list_del(&es->list); | |
407 | free(es); | |
408 | } | |
409 | } | |
410 | ||
411 | static int save_arch_std_events(void *data, char *name, char *event, | |
412 | char *desc, char *long_desc, char *pmu, | |
413 | char *unit, char *perpkg, char *metric_expr, | |
414 | char *metric_name, char *metric_group) | |
415 | { | |
416 | struct event_struct *es; | |
417 | struct stat *sb = data; | |
418 | ||
419 | es = malloc(sizeof(*es)); | |
420 | if (!es) | |
421 | return -ENOMEM; | |
422 | memset(es, 0, sizeof(*es)); | |
423 | FOR_ALL_EVENT_STRUCT_FIELDS(ADD_EVENT_FIELD); | |
424 | list_add_tail(&es->list, &arch_std_events); | |
425 | return 0; | |
426 | out_free: | |
427 | FOR_ALL_EVENT_STRUCT_FIELDS(FREE_EVENT_FIELD); | |
428 | free(es); | |
429 | return -ENOMEM; | |
430 | } | |
431 | ||
80eeb67f AK |
432 | static void print_events_table_suffix(FILE *outfp) |
433 | { | |
434 | fprintf(outfp, "{\n"); | |
435 | ||
436 | fprintf(outfp, "\t.name = 0,\n"); | |
437 | fprintf(outfp, "\t.event = 0,\n"); | |
438 | fprintf(outfp, "\t.desc = 0,\n"); | |
439 | ||
440 | fprintf(outfp, "},\n"); | |
441 | fprintf(outfp, "};\n"); | |
442 | close_table = 0; | |
443 | } | |
444 | ||
0b1db474 AK |
445 | static struct fixed { |
446 | const char *name; | |
447 | const char *event; | |
448 | } fixed[] = { | |
449 | { "inst_retired.any", "event=0xc0" }, | |
72c6ff25 AK |
450 | { "inst_retired.any_p", "event=0xc0" }, |
451 | { "cpu_clk_unhalted.ref", "event=0x0,umask=0x03" }, | |
0b1db474 AK |
452 | { "cpu_clk_unhalted.thread", "event=0x3c" }, |
453 | { "cpu_clk_unhalted.thread_any", "event=0x3c,any=1" }, | |
454 | { NULL, NULL}, | |
455 | }; | |
456 | ||
457 | /* | |
458 | * Handle different fixed counter encodings between JSON and perf. | |
459 | */ | |
460 | static char *real_event(const char *name, char *event) | |
461 | { | |
462 | int i; | |
463 | ||
3ba36d36 AK |
464 | if (!name) |
465 | return NULL; | |
466 | ||
0b1db474 AK |
467 | for (i = 0; fixed[i].name; i++) |
468 | if (!strcasecmp(name, fixed[i].name)) | |
469 | return (char *)fixed[i].event; | |
470 | return event; | |
471 | } | |
472 | ||
e9d32c1b JG |
473 | static int |
474 | try_fixup(const char *fn, char *arch_std, char **event, char **desc, | |
475 | char **name, char **long_desc, char **pmu, char **filter, | |
476 | char **perpkg, char **unit, char **metric_expr, char **metric_name, | |
477 | char **metric_group, unsigned long long eventcode) | |
478 | { | |
479 | /* try to find matching event from arch standard values */ | |
480 | struct event_struct *es; | |
481 | ||
482 | list_for_each_entry(es, &arch_std_events, list) { | |
483 | if (!strcmp(arch_std, es->name)) { | |
484 | if (!eventcode && es->event) { | |
485 | /* allow EventCode to be overridden */ | |
486 | free(*event); | |
487 | *event = NULL; | |
488 | } | |
489 | FOR_ALL_EVENT_STRUCT_FIELDS(TRY_FIXUP_FIELD); | |
490 | return 0; | |
491 | } | |
492 | } | |
493 | ||
494 | pr_err("%s: could not find matching %s for %s\n", | |
495 | prog, arch_std, fn); | |
496 | return -1; | |
497 | } | |
498 | ||
80eeb67f AK |
499 | /* Call func with each event in the json file */ |
500 | int json_events(const char *fn, | |
794ba54a | 501 | int (*func)(void *data, char *name, char *event, char *desc, |
fedb2b51 | 502 | char *long_desc, |
00636c3b | 503 | char *pmu, char *unit, char *perpkg, |
96284814 | 504 | char *metric_expr, |
3ba36d36 | 505 | char *metric_name, char *metric_group), |
80eeb67f AK |
506 | void *data) |
507 | { | |
931ef5dc | 508 | int err; |
80eeb67f AK |
509 | size_t size; |
510 | jsmntok_t *tokens, *tok; | |
511 | int i, j, len; | |
512 | char *map; | |
d5811419 | 513 | char buf[128]; |
80eeb67f AK |
514 | |
515 | if (!fn) | |
516 | return -ENOENT; | |
517 | ||
518 | tokens = parse_json(fn, &map, &size, &len); | |
519 | if (!tokens) | |
520 | return -EIO; | |
521 | EXPECT(tokens->type == JSMN_ARRAY, tokens, "expected top level array"); | |
522 | tok = tokens + 1; | |
523 | for (i = 0; i < tokens->size; i++) { | |
524 | char *event = NULL, *desc = NULL, *name = NULL; | |
794ba54a SB |
525 | char *long_desc = NULL; |
526 | char *extra_desc = NULL; | |
fedb2b51 AK |
527 | char *pmu = NULL; |
528 | char *filter = NULL; | |
529 | char *perpkg = NULL; | |
530 | char *unit = NULL; | |
00636c3b | 531 | char *metric_expr = NULL; |
96284814 | 532 | char *metric_name = NULL; |
3ba36d36 | 533 | char *metric_group = NULL; |
e9d32c1b | 534 | char *arch_std = NULL; |
d5811419 | 535 | unsigned long long eventcode = 0; |
80eeb67f AK |
536 | struct msrmap *msr = NULL; |
537 | jsmntok_t *msrval = NULL; | |
538 | jsmntok_t *precise = NULL; | |
539 | jsmntok_t *obj = tok++; | |
540 | ||
541 | EXPECT(obj->type == JSMN_OBJECT, obj, "expected object"); | |
542 | for (j = 0; j < obj->size; j += 2) { | |
543 | jsmntok_t *field, *val; | |
544 | int nz; | |
00636c3b | 545 | char *s; |
80eeb67f AK |
546 | |
547 | field = tok + j; | |
548 | EXPECT(field->type == JSMN_STRING, tok + j, | |
549 | "Expected field name"); | |
550 | val = tok + j + 1; | |
551 | EXPECT(val->type == JSMN_STRING, tok + j + 1, | |
552 | "Expected string value"); | |
553 | ||
554 | nz = !json_streq(map, val, "0"); | |
555 | if (match_field(map, field, nz, &event, val)) { | |
556 | /* ok */ | |
d5811419 AK |
557 | } else if (json_streq(map, field, "EventCode")) { |
558 | char *code = NULL; | |
559 | addfield(map, &code, "", "", val); | |
560 | eventcode |= strtoul(code, NULL, 0); | |
561 | free(code); | |
fedb2b51 AK |
562 | } else if (json_streq(map, field, "ExtSel")) { |
563 | char *code = NULL; | |
564 | addfield(map, &code, "", "", val); | |
565 | eventcode |= strtoul(code, NULL, 0) << 21; | |
566 | free(code); | |
80eeb67f AK |
567 | } else if (json_streq(map, field, "EventName")) { |
568 | addfield(map, &name, "", "", val); | |
569 | } else if (json_streq(map, field, "BriefDescription")) { | |
570 | addfield(map, &desc, "", "", val); | |
571 | fixdesc(desc); | |
794ba54a SB |
572 | } else if (json_streq(map, field, |
573 | "PublicDescription")) { | |
574 | addfield(map, &long_desc, "", "", val); | |
575 | fixdesc(long_desc); | |
80eeb67f AK |
576 | } else if (json_streq(map, field, "PEBS") && nz) { |
577 | precise = val; | |
578 | } else if (json_streq(map, field, "MSRIndex") && nz) { | |
579 | msr = lookup_msr(map, val); | |
580 | } else if (json_streq(map, field, "MSRValue")) { | |
581 | msrval = val; | |
582 | } else if (json_streq(map, field, "Errata") && | |
583 | !json_streq(map, val, "null")) { | |
794ba54a | 584 | addfield(map, &extra_desc, ". ", |
80eeb67f AK |
585 | " Spec update: ", val); |
586 | } else if (json_streq(map, field, "Data_LA") && nz) { | |
794ba54a | 587 | addfield(map, &extra_desc, ". ", |
80eeb67f AK |
588 | " Supports address when precise", |
589 | NULL); | |
fedb2b51 AK |
590 | } else if (json_streq(map, field, "Unit")) { |
591 | const char *ppmu; | |
fedb2b51 AK |
592 | |
593 | ppmu = field_to_perf(unit_to_pmu, map, val); | |
594 | if (ppmu) { | |
595 | pmu = strdup(ppmu); | |
596 | } else { | |
597 | if (!pmu) | |
598 | pmu = strdup("uncore_"); | |
599 | addfield(map, &pmu, "", "", val); | |
600 | for (s = pmu; *s; s++) | |
601 | *s = tolower(*s); | |
602 | } | |
603 | addfield(map, &desc, ". ", "Unit: ", NULL); | |
604 | addfield(map, &desc, "", pmu, NULL); | |
3401e8d1 | 605 | addfield(map, &desc, "", " ", NULL); |
fedb2b51 AK |
606 | } else if (json_streq(map, field, "Filter")) { |
607 | addfield(map, &filter, "", "", val); | |
608 | } else if (json_streq(map, field, "ScaleUnit")) { | |
609 | addfield(map, &unit, "", "", val); | |
610 | } else if (json_streq(map, field, "PerPkg")) { | |
611 | addfield(map, &perpkg, "", "", val); | |
96284814 AK |
612 | } else if (json_streq(map, field, "MetricName")) { |
613 | addfield(map, &metric_name, "", "", val); | |
3ba36d36 AK |
614 | } else if (json_streq(map, field, "MetricGroup")) { |
615 | addfield(map, &metric_group, "", "", val); | |
00636c3b AK |
616 | } else if (json_streq(map, field, "MetricExpr")) { |
617 | addfield(map, &metric_expr, "", "", val); | |
618 | for (s = metric_expr; *s; s++) | |
619 | *s = tolower(*s); | |
e9d32c1b JG |
620 | } else if (json_streq(map, field, "ArchStdEvent")) { |
621 | addfield(map, &arch_std, "", "", val); | |
622 | for (s = arch_std; *s; s++) | |
623 | *s = tolower(*s); | |
80eeb67f AK |
624 | } |
625 | /* ignore unknown fields */ | |
626 | } | |
627 | if (precise && desc && !strstr(desc, "(Precise Event)")) { | |
628 | if (json_streq(map, precise, "2")) | |
794ba54a SB |
629 | addfield(map, &extra_desc, " ", |
630 | "(Must be precise)", NULL); | |
80eeb67f | 631 | else |
794ba54a | 632 | addfield(map, &extra_desc, " ", |
80eeb67f AK |
633 | "(Precise event)", NULL); |
634 | } | |
d5811419 AK |
635 | snprintf(buf, sizeof buf, "event=%#llx", eventcode); |
636 | addfield(map, &event, ",", buf, NULL); | |
794ba54a SB |
637 | if (desc && extra_desc) |
638 | addfield(map, &desc, " ", extra_desc, NULL); | |
639 | if (long_desc && extra_desc) | |
640 | addfield(map, &long_desc, " ", extra_desc, NULL); | |
fedb2b51 AK |
641 | if (filter) |
642 | addfield(map, &event, ",", filter, NULL); | |
80eeb67f AK |
643 | if (msr != NULL) |
644 | addfield(map, &event, ",", msr->pname, msrval); | |
3ba36d36 AK |
645 | if (name) |
646 | fixname(name); | |
794ba54a | 647 | |
e9d32c1b JG |
648 | if (arch_std) { |
649 | /* | |
650 | * An arch standard event is referenced, so try to | |
651 | * fixup any unassigned values. | |
652 | */ | |
653 | err = try_fixup(fn, arch_std, &event, &desc, &name, | |
654 | &long_desc, &pmu, &filter, &perpkg, | |
655 | &unit, &metric_expr, &metric_name, | |
656 | &metric_group, eventcode); | |
657 | if (err) | |
658 | goto free_strings; | |
659 | } | |
fedb2b51 | 660 | err = func(data, name, real_event(name, event), desc, long_desc, |
3ba36d36 | 661 | pmu, unit, perpkg, metric_expr, metric_name, metric_group); |
e9d32c1b | 662 | free_strings: |
80eeb67f AK |
663 | free(event); |
664 | free(desc); | |
665 | free(name); | |
794ba54a SB |
666 | free(long_desc); |
667 | free(extra_desc); | |
fedb2b51 AK |
668 | free(pmu); |
669 | free(filter); | |
670 | free(perpkg); | |
671 | free(unit); | |
00636c3b | 672 | free(metric_expr); |
96284814 | 673 | free(metric_name); |
3ba36d36 | 674 | free(metric_group); |
e9d32c1b JG |
675 | free(arch_std); |
676 | ||
80eeb67f AK |
677 | if (err) |
678 | break; | |
679 | tok += j; | |
680 | } | |
681 | EXPECT(tok - tokens == len, tok, "unexpected objects at end"); | |
682 | err = 0; | |
683 | out_free: | |
684 | free_json(map, size, tokens); | |
685 | return err; | |
686 | } | |
687 | ||
688 | static char *file_name_to_table_name(char *fname) | |
689 | { | |
690 | unsigned int i; | |
691 | int n; | |
692 | int c; | |
693 | char *tblname; | |
694 | ||
695 | /* | |
696 | * Ensure tablename starts with alphabetic character. | |
697 | * Derive rest of table name from basename of the JSON file, | |
698 | * replacing hyphens and stripping out .json suffix. | |
699 | */ | |
51ce1dcc | 700 | n = asprintf(&tblname, "pme_%s", fname); |
80eeb67f AK |
701 | if (n < 0) { |
702 | pr_info("%s: asprintf() error %s for file %s\n", prog, | |
703 | strerror(errno), fname); | |
704 | return NULL; | |
705 | } | |
706 | ||
707 | for (i = 0; i < strlen(tblname); i++) { | |
708 | c = tblname[i]; | |
709 | ||
51ce1dcc | 710 | if (c == '-' || c == '/') |
80eeb67f AK |
711 | tblname[i] = '_'; |
712 | else if (c == '.') { | |
713 | tblname[i] = '\0'; | |
714 | break; | |
715 | } else if (!isalnum(c) && c != '_') { | |
716 | pr_err("%s: Invalid character '%c' in file name %s\n", | |
717 | prog, c, basename(fname)); | |
718 | free(tblname); | |
719 | tblname = NULL; | |
720 | break; | |
721 | } | |
722 | } | |
723 | ||
724 | return tblname; | |
725 | } | |
726 | ||
727 | static void print_mapping_table_prefix(FILE *outfp) | |
728 | { | |
729 | fprintf(outfp, "struct pmu_events_map pmu_events_map[] = {\n"); | |
730 | } | |
731 | ||
732 | static void print_mapping_table_suffix(FILE *outfp) | |
733 | { | |
734 | /* | |
735 | * Print the terminating, NULL entry. | |
736 | */ | |
737 | fprintf(outfp, "{\n"); | |
738 | fprintf(outfp, "\t.cpuid = 0,\n"); | |
739 | fprintf(outfp, "\t.version = 0,\n"); | |
740 | fprintf(outfp, "\t.type = 0,\n"); | |
741 | fprintf(outfp, "\t.table = 0,\n"); | |
742 | fprintf(outfp, "},\n"); | |
743 | ||
744 | /* and finally, the closing curly bracket for the struct */ | |
745 | fprintf(outfp, "};\n"); | |
746 | } | |
747 | ||
748 | static int process_mapfile(FILE *outfp, char *fpath) | |
749 | { | |
750 | int n = 16384; | |
751 | FILE *mapfp; | |
752 | char *save = NULL; | |
753 | char *line, *p; | |
754 | int line_num; | |
755 | char *tblname; | |
756 | ||
757 | pr_info("%s: Processing mapfile %s\n", prog, fpath); | |
758 | ||
759 | line = malloc(n); | |
760 | if (!line) | |
761 | return -1; | |
762 | ||
763 | mapfp = fopen(fpath, "r"); | |
764 | if (!mapfp) { | |
765 | pr_info("%s: Error %s opening %s\n", prog, strerror(errno), | |
766 | fpath); | |
767 | return -1; | |
768 | } | |
769 | ||
770 | print_mapping_table_prefix(outfp); | |
771 | ||
dc720ffc AK |
772 | /* Skip first line (header) */ |
773 | p = fgets(line, n, mapfp); | |
774 | if (!p) | |
775 | goto out; | |
776 | ||
777 | line_num = 1; | |
80eeb67f AK |
778 | while (1) { |
779 | char *cpuid, *version, *type, *fname; | |
780 | ||
781 | line_num++; | |
782 | p = fgets(line, n, mapfp); | |
783 | if (!p) | |
784 | break; | |
785 | ||
786 | if (line[0] == '#' || line[0] == '\n') | |
787 | continue; | |
788 | ||
789 | if (line[strlen(line)-1] != '\n') { | |
790 | /* TODO Deal with lines longer than 16K */ | |
791 | pr_info("%s: Mapfile %s: line %d too long, aborting\n", | |
792 | prog, fpath, line_num); | |
793 | return -1; | |
794 | } | |
795 | line[strlen(line)-1] = '\0'; | |
796 | ||
fbc2844e | 797 | cpuid = fixregex(strtok_r(p, ",", &save)); |
80eeb67f AK |
798 | version = strtok_r(NULL, ",", &save); |
799 | fname = strtok_r(NULL, ",", &save); | |
800 | type = strtok_r(NULL, ",", &save); | |
801 | ||
802 | tblname = file_name_to_table_name(fname); | |
803 | fprintf(outfp, "{\n"); | |
804 | fprintf(outfp, "\t.cpuid = \"%s\",\n", cpuid); | |
805 | fprintf(outfp, "\t.version = \"%s\",\n", version); | |
806 | fprintf(outfp, "\t.type = \"%s\",\n", type); | |
807 | ||
808 | /* | |
809 | * CHECK: We can't use the type (eg "core") field in the | |
810 | * table name. For us to do that, we need to somehow tweak | |
811 | * the other caller of file_name_to_table(), process_json() | |
812 | * to determine the type. process_json() file has no way | |
813 | * of knowing these are "core" events unless file name has | |
814 | * core in it. If filename has core in it, we can safely | |
815 | * ignore the type field here also. | |
816 | */ | |
817 | fprintf(outfp, "\t.table = %s\n", tblname); | |
818 | fprintf(outfp, "},\n"); | |
819 | } | |
820 | ||
dc720ffc | 821 | out: |
80eeb67f | 822 | print_mapping_table_suffix(outfp); |
80eeb67f AK |
823 | return 0; |
824 | } | |
825 | ||
826 | /* | |
827 | * If we fail to locate/process JSON and map files, create a NULL mapping | |
828 | * table. This would at least allow perf to build even if we can't find/use | |
829 | * the aliases. | |
830 | */ | |
831 | static void create_empty_mapping(const char *output_file) | |
832 | { | |
833 | FILE *outfp; | |
834 | ||
835 | pr_info("%s: Creating empty pmu_events_map[] table\n", prog); | |
836 | ||
837 | /* Truncate file to clear any partial writes to it */ | |
838 | outfp = fopen(output_file, "w"); | |
839 | if (!outfp) { | |
840 | perror("fopen()"); | |
841 | _Exit(1); | |
842 | } | |
843 | ||
844 | fprintf(outfp, "#include \"../../pmu-events/pmu-events.h\"\n"); | |
845 | print_mapping_table_prefix(outfp); | |
846 | print_mapping_table_suffix(outfp); | |
847 | fclose(outfp); | |
848 | } | |
849 | ||
850 | static int get_maxfds(void) | |
851 | { | |
852 | struct rlimit rlim; | |
853 | ||
854 | if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) | |
855 | return min((int)rlim.rlim_max / 2, 512); | |
856 | ||
857 | return 512; | |
858 | } | |
859 | ||
860 | /* | |
861 | * nftw() doesn't let us pass an argument to the processing function, | |
862 | * so use a global variables. | |
863 | */ | |
864 | static FILE *eventsfp; | |
865 | static char *mapfile; | |
866 | ||
51ce1dcc JG |
867 | static int is_leaf_dir(const char *fpath) |
868 | { | |
869 | DIR *d; | |
870 | struct dirent *dir; | |
871 | int res = 1; | |
872 | ||
873 | d = opendir(fpath); | |
874 | if (!d) | |
875 | return 0; | |
876 | ||
877 | while ((dir = readdir(d)) != NULL) { | |
878 | if (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, "..")) | |
879 | continue; | |
880 | ||
881 | if (dir->d_type == DT_DIR) { | |
882 | res = 0; | |
883 | break; | |
884 | } else if (dir->d_type == DT_UNKNOWN) { | |
885 | char path[PATH_MAX]; | |
886 | struct stat st; | |
887 | ||
888 | sprintf(path, "%s/%s", fpath, dir->d_name); | |
889 | if (stat(path, &st)) | |
890 | break; | |
891 | ||
892 | if (S_ISDIR(st.st_mode)) { | |
893 | res = 0; | |
894 | break; | |
895 | } | |
896 | } | |
897 | } | |
898 | ||
899 | closedir(d); | |
900 | ||
901 | return res; | |
902 | } | |
903 | ||
e9d32c1b JG |
904 | static int is_json_file(const char *name) |
905 | { | |
906 | const char *suffix; | |
907 | ||
908 | if (strlen(name) < 5) | |
909 | return 0; | |
910 | ||
911 | suffix = name + strlen(name) - 5; | |
912 | ||
913 | if (strncmp(suffix, ".json", 5) == 0) | |
914 | return 1; | |
915 | return 0; | |
916 | } | |
917 | ||
918 | static int preprocess_arch_std_files(const char *fpath, const struct stat *sb, | |
919 | int typeflag, struct FTW *ftwbuf) | |
920 | { | |
921 | int level = ftwbuf->level; | |
922 | int is_file = typeflag == FTW_F; | |
923 | ||
924 | if (level == 1 && is_file && is_json_file(fpath)) | |
925 | return json_events(fpath, save_arch_std_events, (void *)sb); | |
926 | ||
927 | return 0; | |
928 | } | |
929 | ||
80eeb67f AK |
930 | static int process_one_file(const char *fpath, const struct stat *sb, |
931 | int typeflag, struct FTW *ftwbuf) | |
932 | { | |
51ce1dcc | 933 | char *tblname, *bname; |
80eeb67f AK |
934 | int is_dir = typeflag == FTW_D; |
935 | int is_file = typeflag == FTW_F; | |
936 | int level = ftwbuf->level; | |
937 | int err = 0; | |
938 | ||
51ce1dcc JG |
939 | if (level == 2 && is_dir) { |
940 | /* | |
941 | * For level 2 directory, bname will include parent name, | |
942 | * like vendor/platform. So search back from platform dir | |
943 | * to find this. | |
944 | */ | |
945 | bname = (char *) fpath + ftwbuf->base - 2; | |
946 | for (;;) { | |
947 | if (*bname == '/') | |
948 | break; | |
949 | bname--; | |
950 | } | |
951 | bname++; | |
952 | } else | |
953 | bname = (char *) fpath + ftwbuf->base; | |
954 | ||
80eeb67f AK |
955 | pr_debug("%s %d %7jd %-20s %s\n", |
956 | is_file ? "f" : is_dir ? "d" : "x", | |
957 | level, sb->st_size, bname, fpath); | |
958 | ||
51ce1dcc JG |
959 | /* base dir or too deep */ |
960 | if (level == 0 || level > 3) | |
80eeb67f AK |
961 | return 0; |
962 | ||
51ce1dcc | 963 | |
80eeb67f | 964 | /* model directory, reset topic */ |
51ce1dcc JG |
965 | if ((level == 1 && is_dir && is_leaf_dir(fpath)) || |
966 | (level == 2 && is_dir)) { | |
80eeb67f AK |
967 | if (close_table) |
968 | print_events_table_suffix(eventsfp); | |
969 | ||
970 | /* | |
971 | * Drop file name suffix. Replace hyphens with underscores. | |
972 | * Fail if file name contains any alphanum characters besides | |
973 | * underscores. | |
974 | */ | |
975 | tblname = file_name_to_table_name(bname); | |
976 | if (!tblname) { | |
977 | pr_info("%s: Error determining table name for %s\n", prog, | |
978 | bname); | |
979 | return -1; | |
980 | } | |
981 | ||
982 | print_events_table_prefix(eventsfp, tblname); | |
983 | return 0; | |
984 | } | |
985 | ||
986 | /* | |
987 | * Save the mapfile name for now. We will process mapfile | |
988 | * after processing all JSON files (so we can write out the | |
989 | * mapping table after all PMU events tables). | |
990 | * | |
80eeb67f AK |
991 | */ |
992 | if (level == 1 && is_file) { | |
4c0ab160 JG |
993 | if (!strcmp(bname, "mapfile.csv")) { |
994 | mapfile = strdup(fpath); | |
80eeb67f AK |
995 | return 0; |
996 | } | |
997 | ||
998 | pr_info("%s: Ignoring file %s\n", prog, fpath); | |
999 | return 0; | |
1000 | } | |
1001 | ||
1002 | /* | |
1003 | * If the file name does not have a .json extension, | |
1004 | * ignore it. It could be a readme.txt for instance. | |
1005 | */ | |
1006 | if (is_file) { | |
e9d32c1b | 1007 | if (!is_json_file(bname)) { |
80eeb67f AK |
1008 | pr_info("%s: Ignoring file without .json suffix %s\n", prog, |
1009 | fpath); | |
1010 | return 0; | |
1011 | } | |
1012 | } | |
1013 | ||
6f2f2ca3 | 1014 | if (level > 1 && add_topic(bname)) |
80eeb67f AK |
1015 | return -ENOMEM; |
1016 | ||
1017 | /* | |
1018 | * Assume all other files are JSON files. | |
1019 | * | |
1020 | * If mapfile refers to 'power7_core.json', we create a table | |
1021 | * named 'power7_core'. Any inconsistencies between the mapfile | |
1022 | * and directory tree could result in build failure due to table | |
1023 | * names not being found. | |
1024 | * | |
1025 | * Atleast for now, be strict with processing JSON file names. | |
1026 | * i.e. if JSON file name cannot be mapped to C-style table name, | |
1027 | * fail. | |
1028 | */ | |
1029 | if (is_file) { | |
1030 | struct perf_entry_data data = { | |
1031 | .topic = get_topic(), | |
1032 | .outfp = eventsfp, | |
1033 | }; | |
1034 | ||
1035 | err = json_events(fpath, print_events_table_entry, &data); | |
1036 | ||
1037 | free(data.topic); | |
1038 | } | |
1039 | ||
1040 | return err; | |
1041 | } | |
1042 | ||
1043 | #ifndef PATH_MAX | |
1044 | #define PATH_MAX 4096 | |
1045 | #endif | |
1046 | ||
1047 | /* | |
1048 | * Starting in directory 'start_dirname', find the "mapfile.csv" and | |
1049 | * the set of JSON files for the architecture 'arch'. | |
1050 | * | |
1051 | * From each JSON file, create a C-style "PMU events table" from the | |
1052 | * JSON file (see struct pmu_event). | |
1053 | * | |
1054 | * From the mapfile, create a mapping between the CPU revisions and | |
1055 | * PMU event tables (see struct pmu_events_map). | |
1056 | * | |
1057 | * Write out the PMU events tables and the mapping table to pmu-event.c. | |
80eeb67f AK |
1058 | */ |
1059 | int main(int argc, char *argv[]) | |
1060 | { | |
1061 | int rc; | |
1062 | int maxfds; | |
1063 | char ldirname[PATH_MAX]; | |
1064 | ||
1065 | const char *arch; | |
1066 | const char *output_file; | |
1067 | const char *start_dirname; | |
3f056b66 | 1068 | struct stat stbuf; |
80eeb67f AK |
1069 | |
1070 | prog = basename(argv[0]); | |
1071 | if (argc < 4) { | |
1072 | pr_err("Usage: %s <arch> <starting_dir> <output_file>\n", prog); | |
1073 | return 1; | |
1074 | } | |
1075 | ||
1076 | arch = argv[1]; | |
1077 | start_dirname = argv[2]; | |
1078 | output_file = argv[3]; | |
1079 | ||
1080 | if (argc > 4) | |
1081 | verbose = atoi(argv[4]); | |
1082 | ||
1083 | eventsfp = fopen(output_file, "w"); | |
1084 | if (!eventsfp) { | |
1085 | pr_err("%s Unable to create required file %s (%s)\n", | |
1086 | prog, output_file, strerror(errno)); | |
1087 | return 2; | |
1088 | } | |
1089 | ||
3f056b66 AK |
1090 | sprintf(ldirname, "%s/%s", start_dirname, arch); |
1091 | ||
1092 | /* If architecture does not have any event lists, bail out */ | |
1093 | if (stat(ldirname, &stbuf) < 0) { | |
1094 | pr_info("%s: Arch %s has no PMU event lists\n", prog, arch); | |
1095 | goto empty_map; | |
1096 | } | |
1097 | ||
80eeb67f AK |
1098 | /* Include pmu-events.h first */ |
1099 | fprintf(eventsfp, "#include \"../../pmu-events/pmu-events.h\"\n"); | |
1100 | ||
80eeb67f AK |
1101 | /* |
1102 | * The mapfile allows multiple CPUids to point to the same JSON file, | |
1103 | * so, not sure if there is a need for symlinks within the pmu-events | |
1104 | * directory. | |
1105 | * | |
1106 | * For now, treat symlinks of JSON files as regular files and create | |
1107 | * separate tables for each symlink (presumably, each symlink refers | |
1108 | * to specific version of the CPU). | |
1109 | */ | |
1110 | ||
1111 | maxfds = get_maxfds(); | |
1112 | mapfile = NULL; | |
e9d32c1b JG |
1113 | rc = nftw(ldirname, preprocess_arch_std_files, maxfds, 0); |
1114 | if (rc && verbose) { | |
1115 | pr_info("%s: Error preprocessing arch standard files %s\n", | |
1116 | prog, ldirname); | |
1117 | goto empty_map; | |
1118 | } else if (rc < 0) { | |
1119 | /* Make build fail */ | |
1120 | free_arch_std_events(); | |
1121 | return 1; | |
1122 | } else if (rc) { | |
1123 | goto empty_map; | |
1124 | } | |
1125 | ||
80eeb67f AK |
1126 | rc = nftw(ldirname, process_one_file, maxfds, 0); |
1127 | if (rc && verbose) { | |
1128 | pr_info("%s: Error walking file tree %s\n", prog, ldirname); | |
1129 | goto empty_map; | |
3f056b66 AK |
1130 | } else if (rc < 0) { |
1131 | /* Make build fail */ | |
e9d32c1b | 1132 | free_arch_std_events(); |
3f056b66 | 1133 | return 1; |
80eeb67f AK |
1134 | } else if (rc) { |
1135 | goto empty_map; | |
1136 | } | |
1137 | ||
1138 | if (close_table) | |
1139 | print_events_table_suffix(eventsfp); | |
1140 | ||
1141 | if (!mapfile) { | |
1142 | pr_info("%s: No CPU->JSON mapping?\n", prog); | |
1143 | goto empty_map; | |
1144 | } | |
1145 | ||
1146 | if (process_mapfile(eventsfp, mapfile)) { | |
1147 | pr_info("%s: Error processing mapfile %s\n", prog, mapfile); | |
3f056b66 AK |
1148 | /* Make build fail */ |
1149 | return 1; | |
80eeb67f AK |
1150 | } |
1151 | ||
1152 | return 0; | |
1153 | ||
1154 | empty_map: | |
1155 | fclose(eventsfp); | |
1156 | create_empty_mapping(output_file); | |
e9d32c1b | 1157 | free_arch_std_events(); |
80eeb67f AK |
1158 | return 0; |
1159 | } |