btreplay: remove timestamps
[blktrace.git] / btreplay / btrecord.c
CommitLineData
d47a3fec
AB
1/*
2 * Blktrace record utility - Convert binary trace data into bunches of IOs
3 *
4 * Copyright (C) 2007 Alan D. Brunelle <Alan.Brunelle@hp.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
d47a3fec
AB
21#include <assert.h>
22#include <fcntl.h>
23#include <stdio.h>
24#include <stdlib.h>
25#include <string.h>
26#include <unistd.h>
27#include <sys/param.h>
28#include <sys/stat.h>
29#include <sys/types.h>
30#include <dirent.h>
65a7043b 31#include <stdarg.h>
d47a3fec
AB
32
33#if !defined(_GNU_SOURCE)
34# define _GNU_SOURCE
35#endif
36#include <getopt.h>
37
38#include "list.h"
39#include "btrecord.h"
40#include "blktrace.h"
41
42/*
43 * Per input file information
44 *
45 * @head: Used to link up on input_files
46 * @devnm: Device name portion of this input file
47 * @file_name: Fully qualified name for this input file
48 * @cpu: CPU that this file was collected on
49 * @ifd: Input file descriptor (when opened)
50 * @tpkts: Total number of packets processed.
51 */
52struct ifile_info {
53 struct list_head head;
54 char *devnm, *file_name;
55 int cpu, ifd;
56 __u64 tpkts, genesis;
57};
58
59/*
60 * Per IO trace information
61 *
62 * @time: Time stamp when trace was emitted
63 * @sector: IO sector identifier
64 * @bytes: Number of bytes transferred
65 * @rw: Read (1) or write (0)
66 */
67struct io_spec {
68 __u64 time;
69 __u64 sector;
70 __u32 bytes;
71 int rw;
72};
73
74/*
75 * Per output file information
76 *
77 * @ofp: Output file
78 * @vfp: Verbose output file
79 * @file_name: Fully qualified name for this file
80 * @vfn: Fully qualified name for this file
81 * @cur: Current IO bunch being collected
82 * @iip: Input file this is associated with
83 * @start_time: Start time of th ecurrent bunch
84 * @last_time: Time of last packet put in
85 * @bunches: Number of bunches processed
86 * @pkts: Number of packets stored in bunches
87 */
88struct io_stream {
89 FILE *ofp, *vfp;
90 char *file_name, *vfn;
91 struct io_bunch *cur;
92 struct ifile_info *iip;
93 __u64 start_time, last_time, bunches, pkts;
94};
95
96int data_is_native; // Indicates whether to swap
97static LIST_HEAD(input_files); // List of all input files
98static char *idir = "."; // Input directory base
99static char *odir = "."; // Output directory base
100static char *obase = "replay"; // Output file base
101static __u64 max_bunch_tm = (10 * 1000 * 1000); // 10 milliseconds
102static __u64 max_pkts_per_bunch = 8; // Default # of pkts per bunch
103static int verbose = 0; // Boolean: output stats
104static int find_traces = 0; // Boolean: Find traces in dir
105
106static char usage_str[] = \
107 "\n" \
108 "\t[ -d <dir> : --input-directory=<dir> ] Default: .\n" \
109 "\t[ -D <dir> : --output-directory=<dir>] Default: .\n" \
110 "\t[ -F : --find-traces ] Default: Off\n" \
111 "\t[ -h : --help ] Default: Off\n" \
112 "\t[ -m <nsec> : --max-bunch-time=<nsec> ] Default: 10 msec\n" \
113 "\t[ -M <pkts> : --max-pkts=<pkts> ] Default: 8\n" \
114 "\t[ -o <base> : --output-base=<base> ] Default: replay\n" \
115 "\t[ -v : --verbose ] Default: Off\n" \
116 "\t[ -V : --version ] Default: Off\n" \
117 "\t<dev>... Default: None\n" \
118 "\n";
119
120#define S_OPTS "d:D:Fhm:M:o:vV"
121static struct option l_opts[] = {
122 {
123 .name = "input-directory",
124 .has_arg = required_argument,
125 .flag = NULL,
126 .val = 'd'
127 },
128 {
129 .name = "output-directory",
130 .has_arg = required_argument,
131 .flag = NULL,
132 .val = 'D'
133 },
134 {
135 .name = "find-traces",
136 .has_arg = no_argument,
137 .flag = NULL,
138 .val = 'F'
139 },
140 {
141 .name = "help",
142 .has_arg = no_argument,
143 .flag = NULL,
144 .val = 'h'
145 },
146 {
147 .name = "max-bunch-time",
148 .has_arg = required_argument,
149 .flag = NULL,
150 .val = 'm'
151 },
152 {
306a8ee9 153 .name = "max-pkts",
d47a3fec
AB
154 .has_arg = required_argument,
155 .flag = NULL,
156 .val = 'M'
157 },
158 {
159 .name = "output-base",
160 .has_arg = required_argument,
161 .flag = NULL,
162 .val = 'o'
163 },
164 {
165 .name = "verbose",
166 .has_arg = no_argument,
167 .flag = NULL,
168 .val = 'v'
169 },
170 {
171 .name = "version",
172 .has_arg = no_argument,
173 .flag = NULL,
174 .val = 'V'
175 },
176 {
177 .name = NULL
178 }
179};
180
181#define ERR_ARGS 1
182#define ERR_SYSCALL 2
65a7043b
AB
183static inline void fatal(const char *errstring, const int exitval,
184 const char *fmt, ...)
185{
186 va_list ap;
187
188 if (errstring)
189 perror(errstring);
190
191 va_start(ap, fmt);
192 vfprintf(stderr, fmt, ap);
193 va_end(ap);
194
195 exit(exitval);
196 /*NOTREACHED*/
197}
d47a3fec
AB
198
199/**
200 * match - Return true if this trace is a proper QUEUE transaction
201 * @action: Action field from trace
202 */
203static inline int match(__u32 action)
204{
205 return ((action & 0xffff) == __BLK_TA_QUEUE) &&
206 (action & BLK_TC_ACT(BLK_TC_QUEUE));
207}
208
209/**
210 * usage - Display usage string and version
211 */
212static void usage(void)
213{
214 fprintf(stderr, "Usage: btrecord -- version %s\n%s",
215 my_btversion, usage_str);
216}
217
218/**
219 * write_file_hdr - Seek to and write btrecord file header
220 * @stream: Output file information
221 * @hdr: Header to write
222 */
223static void write_file_hdr(struct io_stream *stream, struct io_file_hdr *hdr)
224{
225 hdr->version = mk_btversion(btver_mjr, btver_mnr, btver_sub);
226
227 if (verbose) {
228 fprintf(stderr, "\t%s: %llx %llx %llx %llx\n",
229 stream->file_name,
230 (long long unsigned)hdr->version,
231 (long long unsigned)hdr->genesis,
232 (long long unsigned)hdr->nbunches,
233 (long long unsigned)hdr->total_pkts);
234 }
235
236 fseek(stream->ofp, 0, SEEK_SET);
237 if (fwrite(hdr, sizeof(*hdr), 1, stream->ofp) != 1) {
238 fatal(stream->file_name, ERR_SYSCALL, "Hdr write failed\n");
239 /*NOTREACHED*/
240 }
241}
242
243/**
244 * io_bunch_create - Allocate & initialize an io_bunch
245 * @io_stream: IO stream being added to
246 * @pre_stall: Amount of time that this bunch should be delayed by
247 * @start_time: Records current start
248 */
249static inline void io_bunch_create(struct io_stream *stream, __u64 start_time)
250{
251 struct io_bunch *cur = malloc(sizeof(*cur));
252
253 memset(cur, 0, sizeof(*cur));
254
255 cur->hdr.npkts = 0;
256 cur->hdr.time_stamp = stream->start_time = start_time;
257
258 stream->cur = cur;
259}
260
261/**
262 * io_bunch_add - Add an IO to the current bunch of IOs
263 * @stream: Per-output file stream information
264 * @spec: IO trace specification
265 *
266 * Returns update bunch information
267 */
268static void io_bunch_add(struct io_stream *stream, struct io_spec *spec)
269{
270 struct io_bunch *cur = stream->cur;
271 struct io_pkt iop = {
272 .sector = spec->sector,
273 .nbytes = spec->bytes,
274 .rw = spec->rw
275 };
276
277 assert(cur != NULL);
278 assert(cur->hdr.npkts < BT_MAX_PKTS);
279 assert(stream->last_time == 0 || stream->last_time <= spec->time);
280
281 cur->pkts[cur->hdr.npkts++] = iop; // Struct copy
282 stream->last_time = spec->time;
283}
284
285/**
286 * rem_input_file - Release resources associated with an input file
287 * @iip: Per-input file information
288 */
289static void rem_input_file(struct ifile_info *iip)
290{
291 list_del(&iip->head);
292
293 close(iip->ifd);
294 free(iip->file_name);
295 free(iip->devnm);
296 free(iip);
297}
298
299/**
300 * __add_input_file - Allocate and initialize per-input file structure
301 * @cpu: CPU for this file
302 * @devnm: Device name for this file
303 * @file_name: Fully qualifed input file name
304 */
305static void __add_input_file(int cpu, char *devnm, char *file_name)
306{
307 struct ifile_info *iip = malloc(sizeof(*iip));
308
309 iip->cpu = cpu;
310 iip->tpkts = 0;
311 iip->genesis = 0;
312 iip->devnm = strdup(devnm);
313 iip->file_name = strdup(file_name);
314 iip->ifd = open(file_name, O_RDONLY);
315 if (iip->ifd < 0) {
316 fatal(file_name, ERR_ARGS, "Unable to open\n");
317 /*NOTREACHED*/
318 }
319
320 list_add_tail(&iip->head, &input_files);
321}
322
323/**
324 * add_input_file - Set up the input file name
325 * @devnm: Device name to use
326 */
327static void add_input_file(char *devnm)
328{
329 struct list_head *p;
330 int cpu, found = 0;
331
332 __list_for_each(p, &input_files) {
333 struct ifile_info *iip = list_entry(p, struct ifile_info, head);
334 if (strcmp(iip->devnm, devnm) == 0)
335 return;
336 }
337
338 for (cpu = 0; ; cpu++) {
339 char full_name[MAXPATHLEN];
340
341 sprintf(full_name, "%s/%s.blktrace.%d", idir, devnm, cpu);
342 if (access(full_name, R_OK) != 0)
343 break;
344
345 __add_input_file(cpu, devnm, full_name);
346 found++;
347 }
348
349 if (!found) {
350 fatal(NULL, ERR_ARGS, "No traces found for %s\n", devnm);
351 /*NOTREACHED*/
352 }
353}
354
355static void find_input_files(char *idir)
356{
357 struct dirent *ent;
358 DIR *dir = opendir(idir);
359
360 if (dir == NULL) {
361 fatal(idir, ERR_ARGS, "Unable to open %s\n", idir);
362 /*NOTREACHED*/
363 }
364
365 while ((ent = readdir(dir)) != NULL) {
6ca1e530 366 char *p, *dsf;
d47a3fec
AB
367
368 if (strstr(ent->d_name, ".blktrace.") == NULL)
369 continue;
370
371 dsf = strdup(ent->d_name);
372 p = index(dsf, '.');
373 assert(p != NULL);
374 *p = '\0';
375 add_input_file(dsf);
376 free(dsf);
377 }
378
379 closedir(dir);
380}
381
382/**
383 * handle_args - Parse passed in argument list
384 * @argc: Number of arguments in argv
385 * @argv: Arguments passed in
386 *
387 * Does rudimentary parameter verification as well.
388 */
389void handle_args(int argc, char *argv[])
390{
391 int c;
392
393 while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) != -1) {
394 switch (c) {
395 case 'd':
396 idir = optarg;
397 if (access(idir, R_OK | X_OK) != 0) {
398 fatal(idir, ERR_ARGS,
399 "Invalid input directory specified\n");
400 /*NOTREACHED*/
401 }
402 break;
403
404 case 'D':
405 odir = optarg;
406 if (access(odir, R_OK | X_OK) != 0) {
407 fatal(odir, ERR_ARGS,
408 "Invalid output directory specified\n");
409 /*NOTREACHED*/
410 }
411 break;
412
413 case 'F':
414 find_traces = 1;
415 break;
416
417 case 'h':
418 usage();
419 exit(0);
420 /*NOTREACHED*/
421
422 case 'm':
423 max_bunch_tm = (__u64)atoll(optarg);
424 if (max_bunch_tm < 1) {
425 fprintf(stderr, "Invalid bunch time %llu\n",
426 (unsigned long long)max_bunch_tm);
427 exit(ERR_ARGS);
428 /*NOTREACHED*/
429 }
430 break;
431
432 case 'M':
433 max_pkts_per_bunch = (__u64)atoll(optarg);
434 if (!((1 <= max_pkts_per_bunch) &&
435 (max_pkts_per_bunch < 513))) {
436 fprintf(stderr, "Invalid max pkts %llu\n",
437 (unsigned long long)max_pkts_per_bunch);
438 exit(ERR_ARGS);
439 /*NOTREACHED*/
440 }
441 break;
442
443 case 'o':
444 obase = optarg;
445 break;
446
447 case 'V':
448 fprintf(stderr, "btrecord -- version %s\n",
449 my_btversion);
d47a3fec
AB
450 exit(0);
451 /*NOTREACHED*/
452
453 case 'v':
454 verbose++;
455 break;
456
457 default:
458 usage();
459 fatal(NULL, ERR_ARGS, "Invalid command line\n");
460 /*NOTREACHED*/
461 }
462 }
463
464 while (optind < argc)
465 add_input_file(argv[optind++]);
466
467 if (find_traces)
468 find_input_files(idir);
469
470 if (list_len(&input_files) == 0) {
471 fatal(NULL, ERR_ARGS, "Missing required input file name(s)\n");
472 /*NOTREACHED*/
473 }
474}
475
476/**
477 * next_io - Retrieve next Q trace from input stream
478 * @iip: Per-input file information
479 * @spec: IO specifier for trace
480 *
481 * Returns 0 on end of file, 1 if valid data returned.
482 */
483static int next_io(struct ifile_info *iip, struct io_spec *spec)
484{
485 ssize_t ret;
486 __u32 action;
487 __u16 pdu_len;
488 struct blk_io_trace t;
489
490again:
491 ret = read(iip->ifd, &t, sizeof(t));
492 if (ret < 0) {
493 fatal(iip->file_name, ERR_SYSCALL, "Read failed\n");
494 /*NOTREACHED*/
495 }
496 else if (ret == 0)
497 return 0;
498 else if (ret < (ssize_t)sizeof(t)) {
499 fprintf(stderr, "WARNING: Short read on %s (%d)\n",
500 iip->file_name, (int)ret);
501 return 0;
502 }
503
504 if (data_is_native == -1)
505 check_data_endianness(t.magic);
506
507 assert(data_is_native >= 0);
508 if (data_is_native) {
509 spec->time = t.time;
510 spec->sector = t.sector;
511 spec->bytes = t.bytes;
512 action = t.action;
513 pdu_len = t.pdu_len;
514 }
515 else {
516 spec->time = be64_to_cpu(t.time);
517 spec->sector = be64_to_cpu(t.sector);
518 spec->bytes = be32_to_cpu(t.bytes);
519 action = be32_to_cpu(t.action);
520 pdu_len = be16_to_cpu(t.pdu_len);
521 }
522
523
524 if (pdu_len) {
525 char buf[pdu_len];
526
527 ret = read(iip->ifd, buf, pdu_len);
528 if (ret < 0) {
529 fatal(iip->file_name, ERR_SYSCALL, "Read PDU failed\n");
530 /*NOTREACHED*/
531 }
532 else if (ret < (ssize_t)pdu_len) {
533 fprintf(stderr, "WARNING: Short PDU read on %s (%d)\n",
534 iip->file_name, (int)ret);
535 return 0;
536 }
537 }
538
539 iip->tpkts++;
540 if (!match(action))
541 goto again;
542
543 spec->rw = (action & BLK_TC_ACT(BLK_TC_READ)) ? 1 : 0;
544 if (verbose > 1)
545 fprintf(stderr, "%2d: %10llu+%10llu (%d) @ %10llx\n",
546 iip->cpu, (long long unsigned)spec->sector,
547 (long long unsigned)spec->bytes / 512LLU,
548 spec->rw, (long long unsigned)spec->time);
549
550 if (iip->genesis == 0) {
551 iip->genesis = spec->time;
552 if (verbose > 1)
553 fprintf(stderr, "\tSetting new genesis: %llx(%d)\n",
554 (long long unsigned)iip->genesis, iip->cpu);
555 }
556 else if (iip->genesis > spec->time)
557 fatal(NULL, ERR_SYSCALL,
558 "Time inversion? %llu ... %llu\n",
559 (long long unsigned )iip->genesis,
560 (long long unsigned )spec->time);
561
562 return 1;
563}
564
565/**
566 * bunch_output_hdr - Output bunch header
567 */
568static inline void bunch_output_hdr(struct io_stream *stream)
569{
570 struct io_bunch_hdr *hdrp = &stream->cur->hdr;
571
572 assert(0 < hdrp->npkts && hdrp->npkts <= BT_MAX_PKTS);
573 if (fwrite(hdrp, sizeof(struct io_bunch_hdr), 1, stream->ofp) != 1) {
574 fatal(stream->file_name, ERR_SYSCALL, "fwrite(hdr) failed\n");
575 /*NOTREACHED*/
576 }
577
578 if (verbose) {
579 __u64 off = hdrp->time_stamp - stream->iip->genesis;
580
581 assert(stream->vfp);
582 fprintf(stream->vfp, "------------------\n");
583 fprintf(stream->vfp, "%4llu.%09llu %3llu\n",
584 (unsigned long long)off / (1000 * 1000 * 1000),
585 (unsigned long long)off % (1000 * 1000 * 1000),
586 (unsigned long long)hdrp->npkts);
587 fprintf(stream->vfp, "------------------\n");
588 }
589}
590
591/**
592 * bunch_output_pkt - Output IO packets
593 */
594static inline void bunch_output_pkts(struct io_stream *stream)
595{
596 struct io_pkt *p = stream->cur->pkts;
597 size_t npkts = stream->cur->hdr.npkts;
598
599 assert(0 < npkts && npkts <= BT_MAX_PKTS);
600 if (fwrite(p, sizeof(struct io_pkt), npkts, stream->ofp) != npkts) {
601 fatal(stream->file_name, ERR_SYSCALL, "fwrite(pkts) failed\n");
602 /*NOTREACHED*/
603 }
604
605 if (verbose) {
606 size_t i;
607
608 assert(stream->vfp);
609 for (i = 0; i < npkts; i++, p++)
610 fprintf(stream->vfp, "\t%1d %10llu\t%10llu\n",
611 p->rw,
612 (unsigned long long)p->sector,
613 (unsigned long long)p->nbytes / 512);
614 }
615}
616
617/**
618 * stream_flush - Flush current bunch of IOs out to the output stream
619 * @stream: Per-output file stream information
620 */
621static void stream_flush(struct io_stream *stream)
622{
623 struct io_bunch *cur = stream->cur;
624
625 if (cur) {
626 if (cur->hdr.npkts) {
627 assert(cur->hdr.npkts <= BT_MAX_PKTS);
628 bunch_output_hdr(stream);
629 bunch_output_pkts(stream);
630
631 stream->bunches++;
632 stream->pkts += cur->hdr.npkts;
633 }
634 free(cur);
635 }
636}
637
638/**
639 * bunch_done - Returns true if current bunch is either full, or next IO is late
640 * @stream: Output stream information
641 * @spec: IO trace specification
642 */
643static inline int bunch_done(struct io_stream *stream, struct io_spec *spec)
644{
645 if (stream->cur->hdr.npkts >= max_pkts_per_bunch)
646 return 1;
647
648 if ((spec->time - stream->start_time) > max_bunch_tm)
649 return 1;
650
651 return 0;
652}
653
654/**
655 * stream_add_io - Add an IO trace to the current stream
656 * @stream: Output stream information
657 * @spec: IO trace specification
658 */
659static void stream_add_io(struct io_stream *stream, struct io_spec *spec)
660{
661
662 if (stream->cur == NULL)
663 io_bunch_create(stream, spec->time);
664 else if (bunch_done(stream, spec)) {
665 stream_flush(stream);
666 io_bunch_create(stream, spec->time);
667 }
668
669 io_bunch_add(stream, spec);
670}
671
672/**
673 * stream_open - Open output stream for specified input stream
674 * @iip: Per-input file information
675 */
676static struct io_stream *stream_open(struct ifile_info *iip)
677{
678 char ofile_name[MAXPATHLEN];
679 struct io_stream *stream = malloc(sizeof(*stream));
680 struct io_file_hdr io_file_hdr = {
681 .genesis = 0,
682 .nbunches = 0,
683 .total_pkts = 0
684 };
685
686 memset(stream, 0, sizeof(*stream));
687
688 sprintf(ofile_name, "%s/%s.%s.%d", odir, iip->devnm, obase, iip->cpu);
689 stream->ofp = fopen(ofile_name, "w");
690 if (!stream->ofp) {
691 fatal(ofile_name, ERR_SYSCALL, "Open failed\n");
692 /*NOTREACHED*/
693 }
694
695 stream->iip = iip;
696 stream->cur = NULL;
697 stream->bunches = stream->pkts = 0;
698 stream->last_time = 0;
699 stream->file_name = strdup(ofile_name);
700
701 write_file_hdr(stream, &io_file_hdr);
702
703 if (verbose) {
704 char vfile_name[MAXPATHLEN];
705
706 sprintf(vfile_name, "%s/%s.%s.%d.rec", odir, iip->devnm,
707 obase, iip->cpu);
708 stream->vfp = fopen(vfile_name, "w");
709 if (!stream->vfp) {
710 fatal(vfile_name, ERR_SYSCALL, "Open failed\n");
711 /*NOTREACHED*/
712 }
713
714 stream->vfn = strdup(vfile_name);
715 }
716
717 data_is_native = -1;
718 return stream;
719}
720
721/**
722 * stream_close - Release resources associated with an output stream
723 * @stream: Stream to release
724 */
725static void stream_close(struct io_stream *stream)
726{
727 struct io_file_hdr io_file_hdr = {
728 .genesis = stream->iip->genesis,
729 .nbunches = stream->bunches,
730 .total_pkts = stream->pkts
731 };
732
733 stream_flush(stream);
734 write_file_hdr(stream, &io_file_hdr);
735 fclose(stream->ofp);
736
737 if (verbose && stream->bunches) {
738 fprintf(stderr,
739 "%s:%d: %llu pkts (tot), %llu pkts (replay), "
740 "%llu bunches, %.1lf pkts/bunch\n",
741 stream->iip->devnm, stream->iip->cpu,
742 (unsigned long long)stream->iip->tpkts,
743 (unsigned long long)stream->pkts,
744 (unsigned long long)stream->bunches,
745 (double)(stream->pkts) / (double)(stream->bunches));
746
747 fclose(stream->vfp);
748 free(stream->vfn);
749 }
750
751 free(stream->file_name);
752 free(stream);
753}
754
755/**
756 * process - Process one input file to an output file
757 * @iip: Per-input file information
758 */
759static void process(struct ifile_info *iip)
760{
761 struct io_spec spec;
762 struct io_stream *stream;
763
764 stream = stream_open(iip);
765 while (next_io(iip, &spec))
766 stream_add_io(stream, &spec);
767 stream_close(stream);
768
769 rem_input_file(iip);
770}
771
772/**
773 * main -
774 * @argc: Number of arguments
775 * @argv: Array of arguments
776 */
777int main(int argc, char *argv[])
778{
779 struct list_head *p, *q;
780
781 handle_args(argc, argv);
782 list_for_each_safe(p, q, &input_files)
783 process(list_entry(p, struct ifile_info, head));
784
785 return 0;
786}