Commit | Line | Data |
---|---|---|
71d5d4c9 JA |
1 | /* |
2 | * This file contains format parsing code for blkparse, allowing you to | |
3 | * customize the individual action format and generel output format. | |
4 | */ | |
5 | #include <stdio.h> | |
6 | #include <string.h> | |
7 | #include <stdlib.h> | |
8 | #include <unistd.h> | |
9 | #include <ctype.h> | |
7bd4fd0a | 10 | #include <time.h> |
71d5d4c9 JA |
11 | |
12 | #include "blktrace.h" | |
13 | ||
4c446dc0 | 14 | #define VALID_SPECS "ABCDFGIMPQRSTUWX" |
71d5d4c9 JA |
15 | |
16 | #define HEADER "%D %2c %8s %5T.%9t %5p %2a %3d " | |
17 | ||
18 | static char *override_format[256]; | |
19 | ||
20 | static inline int valid_spec(int spec) | |
21 | { | |
22 | return strchr(VALID_SPECS, spec) != NULL; | |
23 | } | |
24 | ||
e820abd7 | 25 | void set_all_format_specs(char *option) |
71d5d4c9 JA |
26 | { |
27 | char *p; | |
28 | ||
29 | for (p = VALID_SPECS; *p; p++) | |
30 | if (override_format[(int)(*p)] == NULL) | |
e820abd7 | 31 | override_format[(int)(*p)] = strdup(option); |
71d5d4c9 JA |
32 | } |
33 | ||
e820abd7 | 34 | int add_format_spec(char *option) |
71d5d4c9 JA |
35 | { |
36 | int spec = optarg[0]; | |
37 | ||
38 | if (!valid_spec(spec)) { | |
39 | fprintf(stderr,"Bad format specifier %c\n", spec); | |
40 | return 1; | |
41 | } | |
42 | if (optarg[1] != ',') { | |
e820abd7 | 43 | fprintf(stderr,"Bad format specifier - need ',' %s\n", option); |
71d5d4c9 JA |
44 | return 1; |
45 | } | |
e820abd7 | 46 | option += 2; |
71d5d4c9 | 47 | |
0ac4c20e | 48 | override_format[spec] = strdup(option); |
71d5d4c9 JA |
49 | |
50 | return 0; | |
51 | } | |
52 | ||
768277cc JA |
53 | static inline void fill_rwbs(char *rwbs, struct blk_io_trace *t) |
54 | { | |
55 | int w = t->action & BLK_TC_ACT(BLK_TC_WRITE); | |
f86990ed | 56 | int a = t->action & BLK_TC_ACT(BLK_TC_AHEAD); |
768277cc | 57 | int s = t->action & BLK_TC_ACT(BLK_TC_SYNC); |
711e4d25 | 58 | int m = t->action & BLK_TC_ACT(BLK_TC_META); |
37081bf5 | 59 | int d = t->action & BLK_TC_ACT(BLK_TC_DISCARD); |
34432093 NK |
60 | int f = t->action & BLK_TC_ACT(BLK_TC_FLUSH); |
61 | int u = t->action & BLK_TC_ACT(BLK_TC_FUA); | |
768277cc JA |
62 | int i = 0; |
63 | ||
34432093 NK |
64 | if (f) |
65 | rwbs[i++] = 'F'; /* flush */ | |
66 | ||
37081bf5 DW |
67 | if (d) |
68 | rwbs[i++] = 'D'; | |
69 | else if (w) | |
768277cc | 70 | rwbs[i++] = 'W'; |
d57450af | 71 | else if (t->bytes) |
768277cc | 72 | rwbs[i++] = 'R'; |
d57450af JA |
73 | else |
74 | rwbs[i++] = 'N'; | |
34432093 NK |
75 | |
76 | if (u) | |
77 | rwbs[i++] = 'F'; /* fua */ | |
f86990ed NS |
78 | if (a) |
79 | rwbs[i++] = 'A'; | |
768277cc JA |
80 | if (s) |
81 | rwbs[i++] = 'S'; | |
711e4d25 JA |
82 | if (m) |
83 | rwbs[i++] = 'M'; | |
768277cc JA |
84 | |
85 | rwbs[i] = '\0'; | |
86 | } | |
87 | ||
7bd4fd0a OK |
88 | static const char * |
89 | print_time(unsigned long long timestamp) | |
90 | { | |
91 | static char timebuf[128]; | |
92 | struct tm *tm; | |
93 | time_t sec; | |
94 | unsigned long nsec; | |
95 | ||
96 | sec = abs_start_time.tv_sec + SECONDS(timestamp); | |
97 | nsec = abs_start_time.tv_nsec + NANO_SECONDS(timestamp); | |
98 | if (nsec >= 1000000000) { | |
99 | nsec -= 1000000000; | |
100 | sec += 1; | |
101 | } | |
102 | ||
103 | tm = localtime(&sec); | |
104 | snprintf(timebuf, sizeof(timebuf), | |
105 | "%02u:%02u:%02u.%06lu", | |
106 | tm->tm_hour, | |
107 | tm->tm_min, | |
108 | tm->tm_sec, | |
109 | nsec / 1000); | |
110 | return timebuf; | |
111 | } | |
112 | ||
63756b50 | 113 | static inline int pdu_rest_is_zero(unsigned char *pdu, int len) |
1d6376a5 | 114 | { |
63756b50 | 115 | static char zero[4096]; |
1d6376a5 | 116 | |
63756b50 | 117 | return !memcmp(pdu, zero, len); |
1d6376a5 JA |
118 | } |
119 | ||
768277cc JA |
120 | static char *dump_pdu(unsigned char *pdu_buf, int pdu_len) |
121 | { | |
122 | static char p[4096]; | |
123 | int i, len; | |
124 | ||
125 | if (!pdu_buf || !pdu_len) | |
126 | return NULL; | |
127 | ||
128 | for (len = 0, i = 0; i < pdu_len; i++) { | |
129 | if (i) | |
130 | len += sprintf(p + len, " "); | |
131 | ||
132 | len += sprintf(p + len, "%02x", pdu_buf[i]); | |
1d6376a5 JA |
133 | |
134 | /* | |
135 | * usually dump for cdb dumps where we can see lots of | |
136 | * zeroes, stop when the rest is just zeroes and indicate | |
137 | * so with a .. appended | |
138 | */ | |
139 | if (!pdu_buf[i] && pdu_rest_is_zero(pdu_buf + i, pdu_len - i)) { | |
140 | sprintf(p + len, " .."); | |
141 | break; | |
142 | } | |
768277cc JA |
143 | } |
144 | ||
145 | return p; | |
146 | } | |
147 | ||
a8f30e64 JA |
148 | #define pdu_start(t) (((void *) (t) + sizeof(struct blk_io_trace))) |
149 | ||
768277cc JA |
150 | static unsigned int get_pdu_int(struct blk_io_trace *t) |
151 | { | |
a8f30e64 | 152 | __u64 *val = pdu_start(t); |
768277cc JA |
153 | |
154 | return be64_to_cpu(*val); | |
155 | } | |
156 | ||
a8f30e64 JA |
157 | static void get_pdu_remap(struct blk_io_trace *t, struct blk_io_trace_remap *r) |
158 | { | |
159 | struct blk_io_trace_remap *__r = pdu_start(t); | |
580f7440 | 160 | __u64 sector_from = __r->sector_from; |
a8f30e64 | 161 | |
97d13fb0 | 162 | r->device_from = be32_to_cpu(__r->device_from); |
580f7440 AB |
163 | r->device_to = be32_to_cpu(__r->device_to); |
164 | r->sector_from = be64_to_cpu(sector_from); | |
a8f30e64 JA |
165 | } |
166 | ||
71d5d4c9 JA |
167 | static void print_field(char *act, struct per_cpu_info *pci, |
168 | struct blk_io_trace *t, unsigned long long elapsed, | |
169 | int pdu_len, unsigned char *pdu_buf, char field, | |
170 | int minus, int has_w, int width) | |
171 | { | |
172 | char format[64]; | |
173 | ||
174 | if (has_w) { | |
175 | if (minus) | |
176 | sprintf(format, "%%-%d", width); | |
177 | else | |
178 | sprintf(format, "%%%d", width); | |
179 | } else | |
180 | sprintf(format, "%%"); | |
181 | ||
182 | switch (field) { | |
183 | case 'a': | |
184 | fprintf(ofp, strcat(format, "s"), act); | |
185 | break; | |
186 | case 'c': | |
187 | fprintf(ofp, strcat(format, "d"), pci->cpu); | |
188 | break; | |
bfc70ad5 JA |
189 | case 'C': { |
190 | char *name = find_process_name(t->pid); | |
191 | ||
192 | fprintf(ofp, strcat(format, "s"), name); | |
71d5d4c9 | 193 | break; |
bfc70ad5 | 194 | } |
71d5d4c9 | 195 | case 'd': { |
34432093 | 196 | char rwbs[8]; |
768277cc JA |
197 | |
198 | fill_rwbs(rwbs, t); | |
71d5d4c9 JA |
199 | fprintf(ofp, strcat(format, "s"), rwbs); |
200 | break; | |
201 | } | |
202 | case 'D': /* format width ignored */ | |
203 | fprintf(ofp,"%3d,%-3d", MAJOR(t->device), MINOR(t->device)); | |
204 | break; | |
205 | case 'e': | |
206 | fprintf(ofp, strcat(format, "d"), t->error); | |
207 | break; | |
7238673f JK |
208 | case 'g': { |
209 | char cgidstr[24]; | |
210 | u32 ino = 0, gen = 0; | |
211 | ||
212 | if (t->action & __BLK_TA_CGROUP) { | |
213 | struct blk_io_cgroup_payload *cgid = | |
214 | (struct blk_io_cgroup_payload *)pdu_buf; | |
215 | ||
216 | ino = cgid->ino; | |
217 | gen = cgid->gen; | |
218 | } | |
219 | sprintf(cgidstr, "%x,%x", ino, gen); | |
220 | fprintf(ofp, strcat(format, "s"), cgidstr); | |
221 | break; | |
222 | } | |
71d5d4c9 JA |
223 | case 'M': |
224 | fprintf(ofp, strcat(format, "d"), MAJOR(t->device)); | |
225 | break; | |
226 | case 'm': | |
227 | fprintf(ofp, strcat(format, "d"), MINOR(t->device)); | |
228 | break; | |
229 | case 'n': | |
ae957cbc | 230 | fprintf(ofp, strcat(format, "u"), t_sec(t)); |
71d5d4c9 | 231 | break; |
1c8ca7b5 JA |
232 | case 'N': |
233 | fprintf(ofp, strcat(format, "u"), t->bytes); | |
234 | break; | |
71d5d4c9 JA |
235 | case 'p': |
236 | fprintf(ofp, strcat(format, "u"), t->pid); | |
237 | break; | |
768277cc JA |
238 | case 'P': { /* format width ignored */ |
239 | char *p = dump_pdu(pdu_buf, pdu_len); | |
240 | if (p) | |
241 | fprintf(ofp, "%s", p); | |
71d5d4c9 | 242 | break; |
768277cc | 243 | } |
71d5d4c9 JA |
244 | case 's': |
245 | fprintf(ofp, strcat(format, "ld"), t->sequence); | |
246 | break; | |
247 | case 'S': | |
248 | fprintf(ofp, strcat(format, "lu"), t->sector); | |
249 | break; | |
250 | case 't': | |
251 | sprintf(format, "%%0%dlu", has_w ? width : 9); | |
252 | fprintf(ofp, format, NANO_SECONDS(t->time)); | |
253 | break; | |
254 | case 'T': | |
255 | fprintf(ofp, strcat(format, "d"), SECONDS(t->time)); | |
256 | break; | |
257 | case 'u': | |
258 | if (elapsed == -1ULL) { | |
259 | fprintf(stderr, "Expecting elapsed value\n"); | |
260 | exit(1); | |
261 | } | |
262 | fprintf(ofp, strcat(format, "llu"), elapsed / 1000); | |
263 | break; | |
7bd4fd0a | 264 | case 'U': |
768277cc | 265 | fprintf(ofp, strcat(format, "u"), get_pdu_int(t)); |
71d5d4c9 | 266 | break; |
7bd4fd0a OK |
267 | case 'z': |
268 | fprintf(ofp, strcat(format, "s"), print_time(t->time)); | |
269 | break; | |
71d5d4c9 JA |
270 | default: |
271 | fprintf(ofp,strcat(format, "c"), field); | |
272 | break; | |
273 | } | |
274 | } | |
275 | ||
768277cc JA |
276 | static char *parse_field(char *act, struct per_cpu_info *pci, |
277 | struct blk_io_trace *t, unsigned long long elapsed, | |
278 | int pdu_len, unsigned char *pdu_buf, | |
2ec0bb0b | 279 | char *primary_format) |
71d5d4c9 JA |
280 | { |
281 | int minus = 0; | |
282 | int has_w = 0; | |
283 | int width = 0; | |
2ec0bb0b | 284 | char *p = primary_format; |
71d5d4c9 JA |
285 | |
286 | if (*p == '-') { | |
287 | minus = 1; | |
288 | p++; | |
289 | } | |
290 | if (isdigit(*p)) { | |
291 | has_w = 1; | |
292 | do { | |
293 | width = (width * 10) + (*p++ - '0'); | |
294 | } while ((*p) && (isdigit(*p))); | |
295 | } | |
296 | if (*p) { | |
297 | print_field(act, pci, t, elapsed, pdu_len, pdu_buf, *p++, | |
298 | minus, has_w, width); | |
299 | } | |
300 | return p; | |
301 | } | |
302 | ||
768277cc JA |
303 | static void process_default(char *act, struct per_cpu_info *pci, |
304 | struct blk_io_trace *t, unsigned long long elapsed, | |
305 | int pdu_len, unsigned char *pdu_buf) | |
71d5d4c9 | 306 | { |
580f7440 | 307 | struct blk_io_trace_remap r = { .device_from = 0, }; |
34432093 | 308 | char rwbs[8]; |
bfc70ad5 | 309 | char *name; |
71d5d4c9 | 310 | |
768277cc JA |
311 | fill_rwbs(rwbs, t); |
312 | ||
580f7440 AB |
313 | /* |
314 | * For remaps we have to modify the device using the remap structure | |
315 | * passed up. | |
316 | */ | |
317 | if (act[0] == 'A') { | |
318 | get_pdu_remap(t, &r); | |
319 | t->device = r.device_to; | |
320 | } | |
321 | ||
768277cc JA |
322 | /* |
323 | * The header is always the same | |
324 | */ | |
325 | fprintf(ofp, "%3d,%-3d %2d %8d %5d.%09lu %5u %2s %3s ", | |
326 | MAJOR(t->device), MINOR(t->device), pci->cpu, t->sequence, | |
327 | (int) SECONDS(t->time), (unsigned long) NANO_SECONDS(t->time), | |
328 | t->pid, act, rwbs); | |
71d5d4c9 | 329 | |
bfc70ad5 JA |
330 | name = find_process_name(t->pid); |
331 | ||
768277cc | 332 | switch (act[0]) { |
80c53608 | 333 | case 'R': /* Requeue */ |
71d5d4c9 | 334 | case 'C': /* Complete */ |
768277cc JA |
335 | if (t->action & BLK_TC_ACT(BLK_TC_PC)) { |
336 | char *p = dump_pdu(pdu_buf, pdu_len); | |
337 | if (p) | |
338 | fprintf(ofp, "(%s) ", p); | |
339 | fprintf(ofp, "[%d]\n", t->error); | |
340 | } else { | |
71d5d4c9 | 341 | if (elapsed != -1ULL) { |
d57450af JA |
342 | if (t_sec(t)) |
343 | fprintf(ofp, "%llu + %u (%8llu) [%d]\n", | |
344 | (unsigned long long) t->sector, | |
345 | t_sec(t), elapsed, t->error); | |
346 | else | |
347 | fprintf(ofp, "%llu (%8llu) [%d]\n", | |
348 | (unsigned long long) t->sector, | |
349 | elapsed, t->error); | |
768277cc | 350 | } else { |
d57450af JA |
351 | if (t_sec(t)) |
352 | fprintf(ofp, "%llu + %u [%d]\n", | |
353 | (unsigned long long) t->sector, | |
354 | t_sec(t), t->error); | |
355 | else | |
356 | fprintf(ofp, "%llu [%d]\n", | |
357 | (unsigned long long) t->sector, | |
358 | t->error); | |
768277cc | 359 | } |
71d5d4c9 | 360 | } |
71d5d4c9 JA |
361 | break; |
362 | ||
363 | case 'D': /* Issue */ | |
71d5d4c9 | 364 | case 'I': /* Insert */ |
71d5d4c9 | 365 | case 'Q': /* Queue */ |
bd5e71cf | 366 | case 'B': /* Bounce */ |
768277cc JA |
367 | if (t->action & BLK_TC_ACT(BLK_TC_PC)) { |
368 | char *p; | |
1c8ca7b5 | 369 | fprintf(ofp, "%u ", t->bytes); |
768277cc JA |
370 | p = dump_pdu(pdu_buf, pdu_len); |
371 | if (p) | |
372 | fprintf(ofp, "(%s) ", p); | |
bfc70ad5 | 373 | fprintf(ofp, "[%s]\n", name); |
768277cc JA |
374 | } else { |
375 | if (elapsed != -1ULL) { | |
d57450af JA |
376 | if (t_sec(t)) |
377 | fprintf(ofp, "%llu + %u (%8llu) [%s]\n", | |
378 | (unsigned long long) t->sector, | |
379 | t_sec(t), elapsed, name); | |
380 | else | |
381 | fprintf(ofp, "(%8llu) [%s]\n", elapsed, | |
382 | name); | |
768277cc | 383 | } else { |
d57450af JA |
384 | if (t_sec(t)) |
385 | fprintf(ofp, "%llu + %u [%s]\n", | |
386 | (unsigned long long) t->sector, | |
387 | t_sec(t), name); | |
388 | else | |
389 | fprintf(ofp, "[%s]\n", name); | |
768277cc JA |
390 | } |
391 | } | |
71d5d4c9 JA |
392 | break; |
393 | ||
bd5e71cf | 394 | case 'M': /* Back merge */ |
71d5d4c9 | 395 | case 'F': /* Front merge */ |
768277cc JA |
396 | case 'G': /* Get request */ |
397 | case 'S': /* Sleep request */ | |
d57450af JA |
398 | if (t_sec(t)) |
399 | fprintf(ofp, "%llu + %u [%s]\n", | |
400 | (unsigned long long) t->sector, t_sec(t), name); | |
401 | else | |
402 | fprintf(ofp, "[%s]\n", name); | |
71d5d4c9 JA |
403 | break; |
404 | ||
405 | case 'P': /* Plug */ | |
bfc70ad5 | 406 | fprintf(ofp, "[%s]\n", name); |
71d5d4c9 JA |
407 | break; |
408 | ||
409 | case 'U': /* Unplug IO */ | |
a8f30e64 | 410 | case 'T': /* Unplug timer */ |
bfc70ad5 | 411 | fprintf(ofp, "[%s] %u\n", name, get_pdu_int(t)); |
71d5d4c9 JA |
412 | break; |
413 | ||
97d13fb0 | 414 | case 'A': /* remap */ |
580f7440 | 415 | get_pdu_remap(t, &r); |
a8f30e64 | 416 | fprintf(ofp, "%llu + %u <- (%d,%d) %llu\n", |
e9f883ca | 417 | (unsigned long long) t->sector, t_sec(t), |
580f7440 AB |
418 | MAJOR(r.device_from), MINOR(r.device_from), |
419 | (unsigned long long) r.sector_from); | |
a8f30e64 | 420 | break; |
1a15f6a8 | 421 | |
71d5d4c9 | 422 | case 'X': /* Split */ |
768277cc | 423 | fprintf(ofp, "%llu / %u [%s]\n", (unsigned long long) t->sector, |
bfc70ad5 | 424 | get_pdu_int(t), name); |
71d5d4c9 JA |
425 | break; |
426 | ||
1a15f6a8 AB |
427 | case 'm': /* Message */ |
428 | fprintf(ofp, "%*s\n", pdu_len, pdu_buf); | |
429 | break; | |
430 | ||
71d5d4c9 | 431 | default: |
768277cc JA |
432 | fprintf(stderr, "Unknown action %c\n", act[0]); |
433 | break; | |
71d5d4c9 JA |
434 | } |
435 | ||
71d5d4c9 JA |
436 | } |
437 | ||
438 | void process_fmt(char *act, struct per_cpu_info *pci, struct blk_io_trace *t, | |
439 | unsigned long long elapsed, int pdu_len, | |
440 | unsigned char *pdu_buf) | |
441 | { | |
768277cc JA |
442 | char *p = override_format[(int) *act]; |
443 | ||
444 | if (!p) { | |
445 | process_default(act, pci, t, elapsed, pdu_len, pdu_buf); | |
446 | return; | |
447 | } | |
71d5d4c9 JA |
448 | |
449 | while (*p) { | |
450 | switch (*p) { | |
451 | case '%': /* Field specifier */ | |
452 | p++; | |
453 | if (*p == '%') | |
454 | fprintf(ofp, "%c", *p++); | |
455 | else if (!*p) | |
456 | fprintf(ofp, "%c", '%'); | |
457 | else | |
458 | p = parse_field(act, pci, t, elapsed, | |
459 | pdu_len, pdu_buf, p); | |
460 | break; | |
461 | case '\\': { /* escape */ | |
462 | switch (p[1]) { | |
463 | case 'b': fprintf(ofp, "\b"); break; | |
464 | case 'n': fprintf(ofp, "\n"); break; | |
465 | case 'r': fprintf(ofp, "\r"); break; | |
466 | case 't': fprintf(ofp, "\t"); break; | |
467 | default: | |
34432093 | 468 | fprintf(stderr, |
71d5d4c9 JA |
469 | "Invalid escape char in format %c\n", |
470 | p[1]); | |
471 | exit(1); | |
472 | /*NOTREACHED*/ | |
473 | } | |
474 | p += 2; | |
475 | break; | |
476 | } | |
477 | default: | |
478 | fprintf(ofp, "%c", *p++); | |
479 | break; | |
480 | } | |
481 | } | |
482 | } | |
483 | ||
484 |