A couple of min-counters weren't initialised correctly (thrput_r,
[blktrace.git] / blktrace.c
CommitLineData
d0ca268b
JA
1/*
2 * block queue tracing application
3 *
d956a2cd 4 * Copyright (C) 2005 Jens Axboe <axboe@suse.de>
46e37c55 5 * Copyright (C) 2006 Jens Axboe <axboe@kernel.dk>
d956a2cd
JA
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 *
d0ca268b
JA
21 */
22#include <pthread.h>
23#include <sys/types.h>
24#include <sys/stat.h>
25#include <unistd.h>
26#include <locale.h>
27#include <signal.h>
28#include <fcntl.h>
29#include <string.h>
30#include <sys/ioctl.h>
b9d4294e 31#include <sys/param.h>
e3e74029 32#include <sys/statfs.h>
eb3c8108 33#include <sys/poll.h>
b7106311 34#include <sys/mman.h>
8e86c98a 35#include <sys/socket.h>
d0ca268b
JA
36#include <stdio.h>
37#include <stdlib.h>
38#include <sched.h>
d39c04ca
AB
39#include <ctype.h>
40#include <getopt.h>
da39451f 41#include <errno.h>
8e86c98a
JA
42#include <netinet/in.h>
43#include <arpa/inet.h>
44#include <netdb.h>
32f18c48 45#include <sys/sendfile.h>
d0ca268b
JA
46
47#include "blktrace.h"
21f55651 48#include "barrier.h"
d0ca268b 49
07b96824 50static char blktrace_version[] = "1.0.0";
52724a0e 51
8f551a39
JA
52/*
53 * You may want to increase this even more, if you are logging at a high
54 * rate and see skipped/missed events
55 */
007c233c 56#define BUF_SIZE (512 * 1024)
d0ca268b
JA
57#define BUF_NR (4)
58
007c233c
JA
59#define OFILE_BUF (128 * 1024)
60
3d06efea 61#define DEBUGFS_TYPE 0x64626720
e3e74029 62
5d4f19d9 63#define S_OPTS "d:a:A:r:o:kw:vVb:n:D:lh:p:sI:"
d5396421 64static struct option l_opts[] = {
5c86134e 65 {
d39c04ca 66 .name = "dev",
428683db 67 .has_arg = required_argument,
d39c04ca
AB
68 .flag = NULL,
69 .val = 'd'
70 },
cf1edb17
AB
71 {
72 .name = "input-devs",
73 .has_arg = required_argument,
74 .flag = NULL,
75 .val = 'I'
76 },
5c86134e 77 {
d39c04ca 78 .name = "act-mask",
428683db 79 .has_arg = required_argument,
d39c04ca
AB
80 .flag = NULL,
81 .val = 'a'
82 },
5c86134e 83 {
d39c04ca 84 .name = "set-mask",
428683db 85 .has_arg = required_argument,
d39c04ca
AB
86 .flag = NULL,
87 .val = 'A'
88 },
5c86134e 89 {
5270dddd 90 .name = "relay",
428683db 91 .has_arg = required_argument,
5270dddd
JA
92 .flag = NULL,
93 .val = 'r'
94 },
d5396421
JA
95 {
96 .name = "output",
428683db 97 .has_arg = required_argument,
d5396421
JA
98 .flag = NULL,
99 .val = 'o'
100 },
bc39777c
JA
101 {
102 .name = "kill",
428683db 103 .has_arg = no_argument,
bc39777c
JA
104 .flag = NULL,
105 .val = 'k'
106 },
ece238a6
NS
107 {
108 .name = "stopwatch",
428683db 109 .has_arg = required_argument,
ece238a6
NS
110 .flag = NULL,
111 .val = 'w'
112 },
5d4f19d9
JA
113 {
114 .name = "version",
115 .has_arg = no_argument,
116 .flag = NULL,
117 .val = 'v'
118 },
52724a0e
JA
119 {
120 .name = "version",
121 .has_arg = no_argument,
122 .flag = NULL,
57ea8602 123 .val = 'V'
52724a0e 124 },
129aa440 125 {
3f65c585 126 .name = "buffer-size",
129aa440
JA
127 .has_arg = required_argument,
128 .flag = NULL,
129 .val = 'b'
130 },
131 {
3f65c585 132 .name = "num-sub-buffers",
129aa440
JA
133 .has_arg = required_argument,
134 .flag = NULL,
135 .val = 'n'
136 },
d1d7f15f 137 {
3f65c585 138 .name = "output-dir",
d1d7f15f
JA
139 .has_arg = required_argument,
140 .flag = NULL,
141 .val = 'D'
142 },
8e86c98a
JA
143 {
144 .name = "listen",
145 .has_arg = no_argument,
146 .flag = NULL,
147 .val = 'l'
148 },
149 {
150 .name = "host",
151 .has_arg = required_argument,
152 .flag = NULL,
153 .val = 'h'
154 },
155 {
156 .name = "port",
157 .has_arg = required_argument,
158 .flag = NULL,
159 .val = 'p'
160 },
32f18c48 161 {
79971f43 162 .name = "no-sendfile",
32f18c48
JA
163 .has_arg = no_argument,
164 .flag = NULL,
165 .val = 's'
166 },
71ef8b7c
JA
167 {
168 .name = NULL,
169 }
d39c04ca
AB
170};
171
9db17354 172struct tip_subbuf {
9db17354 173 void *buf;
5be4bdaf
JA
174 unsigned int len;
175 unsigned int max_len;
9db17354
JA
176};
177
21f55651
JA
178#define FIFO_SIZE (1024) /* should be plenty big! */
179#define CL_SIZE (128) /* cache line, any bigger? */
180
181struct tip_subbuf_fifo {
182 int tail __attribute__((aligned(CL_SIZE)));
183 int head __attribute__((aligned(CL_SIZE)));
184 struct tip_subbuf *q[FIFO_SIZE];
185};
186
d0ca268b
JA
187struct thread_information {
188 int cpu;
189 pthread_t thread;
b9d4294e
JA
190
191 int fd;
a3e4d330 192 void *fd_buf;
b9d4294e
JA
193 char fn[MAXPATHLEN + 64];
194
007c233c
JA
195 FILE *ofile;
196 char *ofile_buffer;
32f18c48 197 off_t ofile_offset;
9db17354 198 int ofile_stdout;
8e86c98a 199 int ofile_mmap;
007c233c 200
0cc7d25e
JA
201 int (*get_subbuf)(struct thread_information *, unsigned int);
202 int (*flush_subbuf)(struct thread_information *, struct tip_subbuf *);
203 int (*read_data)(struct thread_information *, void *, unsigned int);
204
d0ca268b 205 unsigned long events_processed;
b7106311 206 unsigned long long data_read;
bcbeb60f 207 unsigned long long data_queued;
e7c9f3ff 208 struct device_information *device;
9db17354
JA
209
210 int exited;
211
b7106311
JA
212 /*
213 * piped fifo buffers
214 */
21f55651 215 struct tip_subbuf_fifo fifo;
7de86b12 216 struct tip_subbuf *leftover_ts;
b7106311
JA
217
218 /*
219 * mmap controlled output files
220 */
221 unsigned long long fs_size;
222 unsigned long long fs_max_size;
223 unsigned long fs_off;
224 void *fs_buf;
225 unsigned long fs_buf_len;
ff11d54c
TZ
226
227 struct net_connection *nc;
d0ca268b
JA
228};
229
e7c9f3ff
NS
230struct device_information {
231 int fd;
232 char *path;
233 char buts_name[32];
99c1f5ab 234 volatile int trace_started;
eb3c8108 235 unsigned long drop_count;
e7c9f3ff 236 struct thread_information *threads;
6a6d3f0f
TZ
237 unsigned long buf_size;
238 unsigned long buf_nr;
239 unsigned int page_size;
ff11d54c
TZ
240
241 struct cl_host *ch;
242 u32 cl_id;
243 time_t cl_connect_time;
e7c9f3ff 244};
d0ca268b 245
e7c9f3ff 246static int ncpus;
d0ca268b 247static struct thread_information *thread_information;
e7c9f3ff
NS
248static int ndevs;
249static struct device_information *device_information;
250
251/* command line option globals */
3d06efea 252static char *debugfs_path;
d5396421 253static char *output_name;
d1d7f15f 254static char *output_dir;
5c86134e 255static int act_mask = ~0U;
bc39777c 256static int kill_running_trace;
eb3c8108
JA
257static unsigned long buf_size = BUF_SIZE;
258static unsigned long buf_nr = BUF_NR;
b7106311 259static unsigned int page_size;
d39c04ca 260
e7c9f3ff
NS
261#define is_done() (*(volatile int *)(&done))
262static volatile int done;
263
eb3c8108
JA
264#define is_trace_stopped() (*(volatile int *)(&trace_stopped))
265static volatile int trace_stopped;
266
267#define is_stat_shown() (*(volatile int *)(&stat_shown))
268static volatile int stat_shown;
a3e4d330 269
8e86c98a
JA
270int data_is_native = -1;
271
72ca8801
NS
272static void exit_trace(int status);
273
99c1f5ab
JA
274#define dip_tracing(dip) (*(volatile int *)(&(dip)->trace_started))
275#define dip_set_tracing(dip, v) ((dip)->trace_started = (v))
276
ce020676 277#define __for_each_dip(__d, __di, __e, __i) \
e0a1988b
JA
278 for (__i = 0, __d = __di; __i < __e; __i++, __d++)
279
280#define for_each_dip(__d, __i) \
ce020676 281 __for_each_dip(__d, device_information, ndevs, __i)
e0a1988b 282#define for_each_nc_dip(__nc, __d, __i) \
ff11d54c 283 __for_each_dip(__d, (__nc)->ch->device_information, (__nc)->ch->ndevs, __i)
99c1f5ab 284
ce020676
JA
285#define __for_each_tip(__d, __t, __ncpus, __j) \
286 for (__j = 0, __t = (__d)->threads; __j < __ncpus; __j++, __t++)
9db17354 287#define for_each_tip(__d, __t, __j) \
ce020676 288 __for_each_tip(__d, __t, ncpus, __j)
ff11d54c
TZ
289#define for_each_cl_host(__c) \
290 for (__c = cl_host_list; __c; __c = __c->list_next)
99c1f5ab 291
8e86c98a
JA
292/*
293 * networking stuff follows. we include a magic number so we know whether
294 * to endianness convert or not
295 */
296struct blktrace_net_hdr {
297 u32 magic; /* same as trace magic */
22cd0c02 298 char buts_name[32]; /* trace name */
8e86c98a 299 u32 cpu; /* for which cpu */
22cd0c02 300 u32 max_cpus;
8e86c98a 301 u32 len; /* length of following trace data */
ff11d54c 302 u32 cl_id; /* id for set of client per-cpu connections */
6a6d3f0f
TZ
303 u32 buf_size; /* client buf_size for this trace */
304 u32 buf_nr; /* client buf_nr for this trace */
305 u32 page_size; /* client page_size for this trace */
8e86c98a
JA
306};
307
308#define TRACE_NET_PORT (8462)
309
310enum {
311 Net_none = 0,
312 Net_server,
313 Net_client,
314};
315
316/*
317 * network cmd line params
318 */
319static char hostname[MAXHOSTNAMELEN];
320static int net_port = TRACE_NET_PORT;
321static int net_mode = 0;
79971f43 322static int net_use_sendfile = 1;
8e86c98a 323
ff11d54c
TZ
324struct cl_host {
325 struct cl_host *list_next;
e0a1988b 326 struct in_addr cl_in_addr;
ff11d54c
TZ
327 struct net_connection *net_connections;
328 int nconn;
e0a1988b
JA
329 struct device_information *device_information;
330 int ndevs;
331 int ncpus;
ff11d54c 332 int ndevs_done;
e0a1988b
JA
333};
334
ff11d54c
TZ
335struct net_connection {
336 int in_fd;
337 struct pollfd pfd;
338 time_t connect_time;
339 struct cl_host *ch;
340 int ncpus;
341};
342
343#define NET_MAX_CL_HOSTS (1024)
344static struct cl_host *cl_host_list;
345static int cl_hosts;
e0a1988b 346static int net_connects;
ff11d54c
TZ
347
348static int *net_out_fd;
8e86c98a
JA
349
350static void handle_sigint(__attribute__((__unused__)) int sig)
351{
7035d92d
JA
352 struct device_information *dip;
353 int i;
354
355 /*
356 * stop trace so we can reap currently produced data
357 */
358 for_each_dip(dip, i) {
921b05fe
JA
359 if (dip->fd == -1)
360 continue;
7035d92d
JA
361 if (ioctl(dip->fd, BLKTRACESTOP) < 0)
362 perror("BLKTRACESTOP");
363 }
364
8e86c98a
JA
365 done = 1;
366}
367
eb3c8108
JA
368static int get_dropped_count(const char *buts_name)
369{
370 int fd;
371 char tmp[MAXPATHLEN + 64];
372
373 snprintf(tmp, sizeof(tmp), "%s/block/%s/dropped",
3d06efea 374 debugfs_path, buts_name);
eb3c8108
JA
375
376 fd = open(tmp, O_RDONLY);
377 if (fd < 0) {
378 /*
379 * this may be ok, if the kernel doesn't support dropped counts
380 */
381 if (errno == ENOENT)
382 return 0;
383
384 fprintf(stderr, "Couldn't open dropped file %s\n", tmp);
385 return -1;
386 }
387
388 if (read(fd, tmp, sizeof(tmp)) < 0) {
389 perror(tmp);
390 close(fd);
391 return -1;
392 }
393
394 close(fd);
395
396 return atoi(tmp);
397}
398
e7c9f3ff 399static int start_trace(struct device_information *dip)
d0ca268b
JA
400{
401 struct blk_user_trace_setup buts;
402
1f79c4a0 403 memset(&buts, 0, sizeof(buts));
6a6d3f0f
TZ
404 buts.buf_size = dip->buf_size;
405 buts.buf_nr = dip->buf_nr;
d39c04ca 406 buts.act_mask = act_mask;
d0ca268b 407
ed71a31e
JA
408 if (ioctl(dip->fd, BLKTRACESETUP, &buts) < 0) {
409 perror("BLKTRACESETUP");
410 return 1;
411 }
412
413 if (ioctl(dip->fd, BLKTRACESTART) < 0) {
414 perror("BLKTRACESTART");
d0ca268b
JA
415 return 1;
416 }
417
e7c9f3ff 418 memcpy(dip->buts_name, buts.name, sizeof(dip->buts_name));
99c1f5ab 419 dip_set_tracing(dip, 1);
d0ca268b
JA
420 return 0;
421}
422
e7c9f3ff 423static void stop_trace(struct device_information *dip)
d0ca268b 424{
99c1f5ab
JA
425 if (dip_tracing(dip) || kill_running_trace) {
426 dip_set_tracing(dip, 0);
cf9208ea 427
7035d92d
JA
428 /*
429 * should be stopped, just don't complain if it isn't
430 */
431 ioctl(dip->fd, BLKTRACESTOP);
432
ed71a31e
JA
433 if (ioctl(dip->fd, BLKTRACETEARDOWN) < 0)
434 perror("BLKTRACETEARDOWN");
cf9208ea 435
e7c9f3ff 436 close(dip->fd);
cf9208ea 437 dip->fd = -1;
707b0914 438 }
d0ca268b
JA
439}
440
e7c9f3ff
NS
441static void stop_all_traces(void)
442{
443 struct device_information *dip;
444 int i;
445
eb3c8108
JA
446 for_each_dip(dip, i) {
447 dip->drop_count = get_dropped_count(dip->buts_name);
e7c9f3ff 448 stop_trace(dip);
eb3c8108 449 }
e7c9f3ff
NS
450}
451
ff11d54c 452static void wait_for_data(struct thread_information *tip, int timeout)
eb3c8108 453{
ff11d54c 454 struct pollfd pfd = { .fd = tip->fd, .events = POLLIN };
eb3c8108 455
ff11d54c
TZ
456 while (!is_done()) {
457 if (poll(&pfd, 1, timeout) < 0) {
7934e668
JA
458 perror("poll");
459 break;
460 }
ff11d54c 461 if (pfd.revents & POLLIN)
9db17354
JA
462 break;
463 if (tip->ofile_stdout)
464 break;
ff11d54c 465 }
eb3c8108
JA
466}
467
0cc7d25e
JA
468static int read_data_file(struct thread_information *tip, void *buf,
469 unsigned int len)
d0ca268b 470{
ae9f71b3 471 int ret = 0;
bbabf03a 472
9db17354 473 do {
ff11d54c 474 wait_for_data(tip, 100);
ae9f71b3 475
9db17354
JA
476 ret = read(tip->fd, buf, len);
477 if (!ret)
478 continue;
479 else if (ret > 0)
480 return ret;
481 else {
bbabf03a 482 if (errno != EAGAIN) {
a3e4d330
JA
483 perror(tip->fn);
484 fprintf(stderr,"Thread %d failed read of %s\n",
485 tip->cpu, tip->fn);
486 break;
487 }
9db17354 488 continue;
bbabf03a 489 }
9db17354 490 } while (!is_done());
8a43bac5 491
bbabf03a 492 return ret;
8e86c98a 493
8a43bac5
JA
494}
495
0cc7d25e
JA
496static int read_data_net(struct thread_information *tip, void *buf,
497 unsigned int len)
8e86c98a 498{
ff11d54c 499 struct net_connection *nc = tip->nc;
8e86c98a
JA
500 unsigned int bytes_left = len;
501 int ret = 0;
502
503 do {
e0a1988b 504 ret = recv(nc->in_fd, buf, bytes_left, MSG_WAITALL);
8e86c98a
JA
505
506 if (!ret)
507 continue;
508 else if (ret < 0) {
509 if (errno != EAGAIN) {
510 perror(tip->fn);
511 fprintf(stderr, "server: failed read\n");
512 return 0;
513 }
514 continue;
515 } else {
516 buf += ret;
517 bytes_left -= ret;
518 }
519 } while (!is_done() && bytes_left);
520
410d7c62 521 return len - bytes_left;
8e86c98a
JA
522}
523
8e86c98a
JA
524static inline struct tip_subbuf *
525subbuf_fifo_dequeue(struct thread_information *tip)
a3e4d330 526{
21f55651
JA
527 const int head = tip->fifo.head;
528 const int next = (head + 1) & (FIFO_SIZE - 1);
529
530 if (head != tip->fifo.tail) {
531 struct tip_subbuf *ts = tip->fifo.q[head];
532
533 store_barrier();
534 tip->fifo.head = next;
535 return ts;
536 }
537
538 return NULL;
9db17354 539}
eb3c8108 540
21f55651
JA
541static inline int subbuf_fifo_queue(struct thread_information *tip,
542 struct tip_subbuf *ts)
9db17354 543{
21f55651
JA
544 const int tail = tip->fifo.tail;
545 const int next = (tail + 1) & (FIFO_SIZE - 1);
546
547 if (next != tip->fifo.head) {
548 tip->fifo.q[tail] = ts;
549 store_barrier();
550 tip->fifo.tail = next;
551 return 0;
552 }
553
554 fprintf(stderr, "fifo too small!\n");
555 return 1;
a3e4d330
JA
556}
557
b7106311
JA
558/*
559 * For file output, truncate and mmap the file appropriately
560 */
8e86c98a 561static int mmap_subbuf(struct thread_information *tip, unsigned int maxlen)
b7106311
JA
562{
563 int ofd = fileno(tip->ofile);
564 int ret;
6a6d3f0f 565 unsigned long nr;
b7106311
JA
566
567 /*
568 * extend file, if we have to. use chunks of 16 subbuffers.
569 */
6a6d3f0f 570 if (tip->fs_off + maxlen > tip->fs_buf_len) {
b7106311 571 if (tip->fs_buf) {
5975d309 572 munlock(tip->fs_buf, tip->fs_buf_len);
b7106311
JA
573 munmap(tip->fs_buf, tip->fs_buf_len);
574 tip->fs_buf = NULL;
575 }
576
6a6d3f0f
TZ
577 tip->fs_off = tip->fs_size & (tip->device->page_size - 1);
578 nr = max(16, tip->device->buf_nr);
579 tip->fs_buf_len = (nr * tip->device->buf_size) - tip->fs_off;
b7106311
JA
580 tip->fs_max_size += tip->fs_buf_len;
581
582 if (ftruncate(ofd, tip->fs_max_size) < 0) {
583 perror("ftruncate");
584 return -1;
585 }
586
587 tip->fs_buf = mmap(NULL, tip->fs_buf_len, PROT_WRITE,
588 MAP_SHARED, ofd, tip->fs_size - tip->fs_off);
589 if (tip->fs_buf == MAP_FAILED) {
590 perror("mmap");
591 return -1;
592 }
5975d309 593 mlock(tip->fs_buf, tip->fs_buf_len);
b7106311
JA
594 }
595
7934e668 596 ret = tip->read_data(tip, tip->fs_buf + tip->fs_off, maxlen);
b7106311 597 if (ret >= 0) {
dbfbd6db 598 tip->data_read += ret;
b7106311
JA
599 tip->fs_size += ret;
600 tip->fs_off += ret;
601 return 0;
602 }
603
604 return -1;
605}
606
18eed2a7
JA
607/*
608 * Use the copy approach for pipes and network
609 */
610static int get_subbuf(struct thread_information *tip, unsigned int maxlen)
611{
612 struct tip_subbuf *ts = malloc(sizeof(*ts));
613 int ret;
614
6a6d3f0f 615 ts->buf = malloc(tip->device->buf_size);
18eed2a7
JA
616 ts->max_len = maxlen;
617
7934e668 618 ret = tip->read_data(tip, ts->buf, ts->max_len);
18eed2a7
JA
619 if (ret > 0) {
620 ts->len = ret;
dbfbd6db 621 tip->data_read += ret;
7035d92d 622 if (subbuf_fifo_queue(tip, ts))
2f064793
JA
623 ret = -1;
624 }
625
626 if (ret <= 0) {
627 free(ts->buf);
628 free(ts);
18eed2a7
JA
629 }
630
631 return ret;
632}
633
9db17354 634static void close_thread(struct thread_information *tip)
a3e4d330 635{
9db17354
JA
636 if (tip->fd != -1)
637 close(tip->fd);
638 if (tip->ofile)
639 fclose(tip->ofile);
640 if (tip->ofile_buffer)
641 free(tip->ofile_buffer);
642 if (tip->fd_buf)
643 free(tip->fd_buf);
1c99bc21 644
9db17354
JA
645 tip->fd = -1;
646 tip->ofile = NULL;
647 tip->ofile_buffer = NULL;
648 tip->fd_buf = NULL;
a3e4d330
JA
649}
650
8e86c98a
JA
651static void tip_ftrunc_final(struct thread_information *tip)
652{
653 /*
654 * truncate to right size and cleanup mmap
655 */
c196b5f2 656 if (tip->ofile_mmap && tip->ofile) {
8e86c98a
JA
657 int ofd = fileno(tip->ofile);
658
659 if (tip->fs_buf)
660 munmap(tip->fs_buf, tip->fs_buf_len);
661
662 ftruncate(ofd, tip->fs_size);
663 }
664}
665
9db17354 666static void *thread_main(void *arg)
a3e4d330 667{
9db17354
JA
668 struct thread_information *tip = arg;
669 pid_t pid = getpid();
670 cpu_set_t cpu_mask;
a3e4d330 671
9db17354
JA
672 CPU_ZERO(&cpu_mask);
673 CPU_SET((tip->cpu), &cpu_mask);
a3e4d330 674
9db17354
JA
675 if (sched_setaffinity(pid, sizeof(cpu_mask), &cpu_mask) == -1) {
676 perror("sched_setaffinity");
677 exit_trace(1);
678 }
a3e4d330 679
9db17354 680 snprintf(tip->fn, sizeof(tip->fn), "%s/block/%s/trace%d",
3d06efea 681 debugfs_path, tip->device->buts_name, tip->cpu);
9db17354
JA
682 tip->fd = open(tip->fn, O_RDONLY);
683 if (tip->fd < 0) {
684 perror(tip->fn);
685 fprintf(stderr,"Thread %d failed open of %s\n", tip->cpu,
686 tip->fn);
687 exit_trace(1);
a3e4d330
JA
688 }
689
b7106311 690 while (!is_done()) {
6a6d3f0f 691 if (tip->get_subbuf(tip, tip->device->buf_size) < 0)
0cc7d25e 692 break;
b7106311
JA
693 }
694
7035d92d
JA
695 /*
696 * trace is stopped, pull data until we get a short read
697 */
6a6d3f0f 698 while (tip->get_subbuf(tip, tip->device->buf_size) > 0)
7035d92d
JA
699 ;
700
8e86c98a
JA
701 tip_ftrunc_final(tip);
702 tip->exited = 1;
703 return NULL;
704}
b7106311 705
8e86c98a
JA
706static int write_data_net(int fd, void *buf, unsigned int buf_len)
707{
708 unsigned int bytes_left = buf_len;
709 int ret;
b7106311 710
8e86c98a
JA
711 while (bytes_left) {
712 ret = send(fd, buf, bytes_left, 0);
713 if (ret < 0) {
714 perror("send");
715 return 1;
716 }
717
718 buf += ret;
719 bytes_left -= ret;
9db17354 720 }
a3e4d330 721
8e86c98a 722 return 0;
a3e4d330
JA
723}
724
32f18c48 725static int net_send_header(struct thread_information *tip, unsigned int len)
8e86c98a
JA
726{
727 struct blktrace_net_hdr hdr;
8e86c98a
JA
728
729 hdr.magic = BLK_IO_TRACE_MAGIC;
22cd0c02 730 strcpy(hdr.buts_name, tip->device->buts_name);
8e86c98a 731 hdr.cpu = tip->cpu;
22cd0c02 732 hdr.max_cpus = ncpus;
32f18c48 733 hdr.len = len;
ff11d54c 734 hdr.cl_id = getpid();
6a6d3f0f
TZ
735 hdr.buf_size = tip->device->buf_size;
736 hdr.buf_nr = tip->device->buf_nr;
737 hdr.page_size = tip->device->page_size;
738
ff11d54c 739 return write_data_net(net_out_fd[tip->cpu], &hdr, sizeof(hdr));
32f18c48 740}
8e86c98a 741
6a752c90
JA
742/*
743 * send header with 0 length to signal end-of-run
744 */
745static void net_client_send_close(void)
746{
7934e668 747 struct device_information *dip;
6a752c90 748 struct blktrace_net_hdr hdr;
7934e668 749 int i;
6a752c90 750
7934e668 751 for_each_dip(dip, i) {
7ab2f837
JA
752 hdr.magic = BLK_IO_TRACE_MAGIC;
753 hdr.max_cpus = ncpus;
754 hdr.len = 0;
7934e668 755 strcpy(hdr.buts_name, dip->buts_name);
7ab2f837 756 hdr.cpu = get_dropped_count(dip->buts_name);
ff11d54c 757 hdr.cl_id = getpid();
6a6d3f0f
TZ
758 hdr.buf_size = dip->buf_size;
759 hdr.buf_nr = dip->buf_nr;
760 hdr.page_size = dip->page_size;
7ab2f837 761
ff11d54c 762 write_data_net(net_out_fd[0], &hdr, sizeof(hdr));
7934e668
JA
763 }
764
6a752c90
JA
765}
766
32f18c48
JA
767static int flush_subbuf_net(struct thread_information *tip,
768 struct tip_subbuf *ts)
769{
770 if (net_send_header(tip, ts->len))
7934e668 771 return -1;
ff11d54c 772 if (write_data_net(net_out_fd[tip->cpu], ts->buf, ts->len))
7934e668 773 return -1;
8e86c98a 774
f0597a7e 775 free(ts->buf);
8e86c98a 776 free(ts);
7934e668 777 return 1;
8e86c98a
JA
778}
779
f6fead25
JA
780static int net_sendfile(struct thread_information *tip, struct tip_subbuf *ts)
781{
ff11d54c 782 int ret = sendfile(net_out_fd[tip->cpu], tip->fd, NULL, ts->len);
11629347
JA
783
784 if (ret < 0) {
785 perror("sendfile");
786 return 1;
787 } else if (ret < (int) ts->len) {
788 fprintf(stderr, "short sendfile send (%d of %d)\n", ret, ts->len);
789 return 1;
790 }
791
792 return 0;
793}
794
32f18c48
JA
795static int flush_subbuf_sendfile(struct thread_information *tip,
796 struct tip_subbuf *ts)
797{
7934e668 798 int ret = -1;
18eed2a7 799
f6fead25 800 if (net_send_header(tip, ts->len))
11629347 801 goto err;
f6fead25 802 if (net_sendfile(tip, ts))
11629347 803 goto err;
32f18c48 804
f6fead25 805 tip->data_read += ts->len;
7934e668 806 ret = 1;
11629347 807err:
32f18c48 808 free(ts);
11629347 809 return ret;
32f18c48
JA
810}
811
ff11d54c 812static int get_subbuf_sendfile(struct thread_information *tip,
d042efdf 813 __attribute__((__unused__)) unsigned int maxlen)
ff11d54c
TZ
814{
815 struct tip_subbuf *ts;
816 struct stat sb;
817 unsigned int ready;
ff11d54c 818
2689be58 819 wait_for_data(tip, -1);
ff11d54c
TZ
820
821 if (fstat(tip->fd, &sb) < 0) {
822 perror("trace stat");
823 return -1;
824 }
4aeec019 825
ff11d54c
TZ
826 ready = sb.st_size - tip->data_queued;
827 if (!ready) {
828 usleep(1000);
829 return 0;
830 }
831
832 ts = malloc(sizeof(*ts));
833 ts->buf = NULL;
834 ts->max_len = 0;
835 ts->len = ready;
836 tip->data_queued += ready;
837
d042efdf
JA
838 if (flush_subbuf_sendfile(tip, ts) < 0)
839 return -1;
ff11d54c
TZ
840
841 return ready;
842}
843
8e86c98a
JA
844static int write_data(struct thread_information *tip, void *buf,
845 unsigned int buf_len)
8a43bac5 846{
7126171a 847 int ret;
8a43bac5 848
6480258a
JA
849 if (!buf_len)
850 return 0;
851
1452478f
JA
852 ret = fwrite(buf, buf_len, 1, tip->ofile);
853 if (ferror(tip->ofile) || ret != 1) {
854 perror("fwrite");
855 clearerr(tip->ofile);
856 return 1;
d0ca268b
JA
857 }
858
9db17354 859 if (tip->ofile_stdout)
7126171a
JA
860 fflush(tip->ofile);
861
8a43bac5
JA
862 return 0;
863}
864
8e86c98a
JA
865static int flush_subbuf_file(struct thread_information *tip,
866 struct tip_subbuf *ts)
8a43bac5 867{
9db17354
JA
868 unsigned int offset = 0;
869 struct blk_io_trace *t;
870 int pdu_len, events = 0;
8a43bac5 871
9db17354 872 /*
7de86b12 873 * surplus from last run
9db17354 874 */
7de86b12
AB
875 if (tip->leftover_ts) {
876 struct tip_subbuf *prev_ts = tip->leftover_ts;
877
9e8b753c 878 if (prev_ts->len + ts->len > prev_ts->max_len) {
7de86b12
AB
879 prev_ts->max_len += ts->len;
880 prev_ts->buf = realloc(prev_ts->buf, prev_ts->max_len);
881 }
882
9e8b753c 883 memcpy(prev_ts->buf + prev_ts->len, ts->buf, ts->len);
7de86b12
AB
884 prev_ts->len += ts->len;
885
886 free(ts->buf);
887 free(ts);
888
889 ts = prev_ts;
890 tip->leftover_ts = NULL;
9db17354 891 }
d0ca268b 892
9db17354
JA
893 while (offset + sizeof(*t) <= ts->len) {
894 t = ts->buf + offset;
3a9d6c13 895
9cfa6c2b
AB
896 if (verify_trace(t)) {
897 write_data(tip, ts->buf, offset);
9db17354 898 return -1;
9cfa6c2b 899 }
3a9d6c13 900
9db17354 901 pdu_len = t->pdu_len;
3a9d6c13 902
9db17354 903 if (offset + sizeof(*t) + pdu_len > ts->len)
3a9d6c13 904 break;
4b5db44a 905
9db17354
JA
906 offset += sizeof(*t) + pdu_len;
907 tip->events_processed++;
b7106311 908 tip->data_read += sizeof(*t) + pdu_len;
9db17354 909 events++;
3a9d6c13
JA
910 }
911
9cfa6c2b
AB
912 if (write_data(tip, ts->buf, offset))
913 return -1;
914
3a9d6c13 915 /*
9db17354 916 * leftover bytes, save them for next time
3a9d6c13 917 */
9db17354 918 if (offset != ts->len) {
7de86b12 919 tip->leftover_ts = ts;
9e8b753c
JA
920 ts->len -= offset;
921 memmove(ts->buf, ts->buf + offset, ts->len);
7de86b12
AB
922 } else {
923 free(ts->buf);
924 free(ts);
9db17354 925 }
4b5db44a 926
9db17354 927 return events;
4b5db44a
JA
928}
929
9db17354 930static int write_tip_events(struct thread_information *tip)
d5396421 931{
21f55651 932 struct tip_subbuf *ts = subbuf_fifo_dequeue(tip);
d5396421 933
0cc7d25e
JA
934 if (ts)
935 return tip->flush_subbuf(tip, ts);
91816d54 936
9db17354 937 return 0;
91816d54
JA
938}
939
9db17354
JA
940/*
941 * scans the tips we know and writes out the subbuffers we accumulate
942 */
943static void get_and_write_events(void)
d0ca268b 944{
9db17354
JA
945 struct device_information *dip;
946 struct thread_information *tip;
27223f19 947 int i, j, events, ret, tips_running;
d0ca268b 948
9db17354
JA
949 while (!is_done()) {
950 events = 0;
d0ca268b 951
9db17354
JA
952 for_each_dip(dip, i) {
953 for_each_tip(dip, tip, j) {
954 ret = write_tip_events(tip);
955 if (ret > 0)
956 events += ret;
957 }
958 }
d0ca268b 959
9db17354 960 if (!events)
7934e668 961 usleep(100000);
d0ca268b
JA
962 }
963
a3e4d330 964 /*
9db17354 965 * reap stored events
a3e4d330 966 */
9db17354
JA
967 do {
968 events = 0;
27223f19 969 tips_running = 0;
9db17354
JA
970 for_each_dip(dip, i) {
971 for_each_tip(dip, tip, j) {
972 ret = write_tip_events(tip);
973 if (ret > 0)
974 events += ret;
27223f19 975 tips_running += !tip->exited;
9db17354 976 }
69e65a9e 977 }
9db17354 978 usleep(10);
27223f19 979 } while (events || tips_running);
d0ca268b
JA
980}
981
b7106311
JA
982static void wait_for_threads(void)
983{
984 /*
8e86c98a
JA
985 * for piped or network output, poll and fetch data for writeout.
986 * for files, we just wait around for trace threads to exit
b7106311 987 */
8e86c98a 988 if ((output_name && !strcmp(output_name, "-")) ||
ff11d54c 989 ((net_mode == Net_client) && !net_use_sendfile))
b7106311
JA
990 get_and_write_events();
991 else {
992 struct device_information *dip;
993 struct thread_information *tip;
994 int i, j, tips_running;
995
996 do {
997 tips_running = 0;
7934e668 998 usleep(100000);
b7106311
JA
999
1000 for_each_dip(dip, i)
1001 for_each_tip(dip, tip, j)
1002 tips_running += !tip->exited;
1003 } while (tips_running);
1004 }
6a752c90
JA
1005
1006 if (net_mode == Net_client)
1007 net_client_send_close();
b7106311
JA
1008}
1009
97159c02
JA
1010static int fill_ofname(struct device_information *dip,
1011 struct thread_information *tip, char *dst,
e3bf54d8 1012 char *buts_name)
8e86c98a 1013{
e3bf54d8 1014 struct stat sb;
8e86c98a
JA
1015 int len = 0;
1016
1017 if (output_dir)
1018 len = sprintf(dst, "%s/", output_dir);
dd870ef6
AB
1019 else
1020 len = sprintf(dst, "./");
8e86c98a 1021
e3bf54d8 1022 if (net_mode == Net_server) {
ff11d54c 1023 struct net_connection *nc = tip->nc;
e0a1988b 1024
ff11d54c
TZ
1025 len += sprintf(dst + len, "%s-", inet_ntoa(nc->ch->cl_in_addr));
1026 len += strftime(dst + len, 64, "%F-%T/", gmtime(&dip->cl_connect_time));
e3bf54d8
JA
1027 }
1028
1029 if (stat(dst, &sb) < 0) {
1030 if (errno != ENOENT) {
1031 perror("stat");
1032 return 1;
1033 }
1034 if (mkdir(dst, 0755) < 0) {
1035 perror(dst);
1036 fprintf(stderr, "Can't make output dir\n");
1037 return 1;
1038 }
1039 }
1040
8e86c98a 1041 if (output_name)
e3bf54d8 1042 sprintf(dst + len, "%s.blktrace.%d", output_name, tip->cpu);
8e86c98a 1043 else
e3bf54d8
JA
1044 sprintf(dst + len, "%s.blktrace.%d", buts_name, tip->cpu);
1045
1046 return 0;
8e86c98a
JA
1047}
1048
0cc7d25e
JA
1049static void fill_ops(struct thread_information *tip)
1050{
1051 /*
1052 * setup ops
1053 */
32f18c48 1054 if (net_mode == Net_client) {
36808255 1055 if (net_use_sendfile) {
32f18c48 1056 tip->get_subbuf = get_subbuf_sendfile;
ff11d54c 1057 tip->flush_subbuf = NULL;
32f18c48
JA
1058 } else {
1059 tip->get_subbuf = get_subbuf;
1060 tip->flush_subbuf = flush_subbuf_net;
1061 }
1062 } else {
1063 if (tip->ofile_mmap)
1064 tip->get_subbuf = mmap_subbuf;
1065 else
1066 tip->get_subbuf = get_subbuf;
0cc7d25e 1067
0cc7d25e 1068 tip->flush_subbuf = flush_subbuf_file;
32f18c48
JA
1069 }
1070
0cc7d25e
JA
1071 if (net_mode == Net_server)
1072 tip->read_data = read_data_net;
1073 else
1074 tip->read_data = read_data_file;
1075}
1076
ddf22842
JA
1077static int tip_open_output(struct device_information *dip,
1078 struct thread_information *tip)
d0ca268b 1079{
ddf22842 1080 int pipeline = output_name && !strcmp(output_name, "-");
8e86c98a 1081 int mode, vbuf_size;
e3bf54d8 1082 char op[128];
d0ca268b 1083
ddf22842
JA
1084 if (net_mode == Net_client) {
1085 tip->ofile = NULL;
1086 tip->ofile_stdout = 0;
1087 tip->ofile_mmap = 0;
0c0b75b4 1088 goto done;
ddf22842
JA
1089 } else if (pipeline) {
1090 tip->ofile = fdopen(STDOUT_FILENO, "w");
1091 tip->ofile_stdout = 1;
1092 tip->ofile_mmap = 0;
1093 mode = _IOLBF;
1094 vbuf_size = 512;
1095 } else {
97159c02 1096 if (fill_ofname(dip, tip, op, dip->buts_name))
e3bf54d8 1097 return 1;
ddf22842
JA
1098 tip->ofile = fopen(op, "w+");
1099 tip->ofile_stdout = 0;
1100 tip->ofile_mmap = 1;
1101 mode = _IOFBF;
1102 vbuf_size = OFILE_BUF;
1103 }
d5396421 1104
0c0b75b4 1105 if (tip->ofile == NULL) {
ddf22842
JA
1106 perror(op);
1107 return 1;
1108 }
d5396421 1109
0c0b75b4
JA
1110 tip->ofile_buffer = malloc(vbuf_size);
1111 if (setvbuf(tip->ofile, tip->ofile_buffer, mode, vbuf_size)) {
1112 perror("setvbuf");
1113 close_thread(tip);
1114 return 1;
ddf22842
JA
1115 }
1116
0c0b75b4 1117done:
ddf22842
JA
1118 fill_ops(tip);
1119 return 0;
1120}
007c233c 1121
ddf22842
JA
1122static int start_threads(struct device_information *dip)
1123{
1124 struct thread_information *tip;
1125 int j;
1126
1127 for_each_tip(dip, tip, j) {
1128 tip->cpu = j;
1129 tip->device = dip;
1130 tip->events_processed = 0;
11eedd9b 1131 tip->fd = -1;
ddf22842
JA
1132 memset(&tip->fifo, 0, sizeof(tip->fifo));
1133 tip->leftover_ts = NULL;
1134
1135 if (tip_open_output(dip, tip))
1136 return 1;
0cc7d25e 1137
9db17354 1138 if (pthread_create(&tip->thread, NULL, thread_main, tip)) {
e7c9f3ff 1139 perror("pthread_create");
007c233c 1140 close_thread(tip);
e7c9f3ff 1141 return 1;
d0ca268b
JA
1142 }
1143 }
1144
e7c9f3ff 1145 return 0;
d0ca268b
JA
1146}
1147
e7c9f3ff 1148static void stop_threads(struct device_information *dip)
3aabcd89 1149{
e7c9f3ff 1150 struct thread_information *tip;
91816d54 1151 unsigned long ret;
007c233c
JA
1152 int i;
1153
9db17354 1154 for_each_tip(dip, tip, i) {
91816d54 1155 (void) pthread_join(tip->thread, (void *) &ret);
9db17354
JA
1156 close_thread(tip);
1157 }
3aabcd89
JA
1158}
1159
e7c9f3ff 1160static void stop_all_threads(void)
72ca8801 1161{
e7c9f3ff 1162 struct device_information *dip;
72ca8801
NS
1163 int i;
1164
99c1f5ab 1165 for_each_dip(dip, i)
e7c9f3ff
NS
1166 stop_threads(dip);
1167}
1168
1169static void stop_all_tracing(void)
1170{
1171 struct device_information *dip;
91816d54 1172 int i;
007c233c 1173
91816d54 1174 for_each_dip(dip, i)
e7c9f3ff 1175 stop_trace(dip);
72ca8801
NS
1176}
1177
1178static void exit_trace(int status)
1179{
eb3c8108
JA
1180 if (!is_trace_stopped()) {
1181 trace_stopped = 1;
1182 stop_all_threads();
1183 stop_all_tracing();
1184 }
1185
72ca8801
NS
1186 exit(status);
1187}
1188
e7c9f3ff
NS
1189static int resize_devices(char *path)
1190{
1191 int size = (ndevs + 1) * sizeof(struct device_information);
1192
1193 device_information = realloc(device_information, size);
1194 if (!device_information) {
1195 fprintf(stderr, "Out of memory, device %s (%d)\n", path, size);
1196 return 1;
1197 }
1198 device_information[ndevs].path = path;
1199 ndevs++;
1200 return 0;
1201}
1202
1203static int open_devices(void)
d0ca268b 1204{
e7c9f3ff 1205 struct device_information *dip;
d0ca268b 1206 int i;
d0ca268b 1207
99c1f5ab 1208 for_each_dip(dip, i) {
cf9208ea 1209 dip->fd = open(dip->path, O_RDONLY | O_NONBLOCK);
e7c9f3ff
NS
1210 if (dip->fd < 0) {
1211 perror(dip->path);
1212 return 1;
1213 }
6a6d3f0f
TZ
1214 dip->buf_size = buf_size;
1215 dip->buf_nr = buf_nr;
1216 dip->page_size = page_size;
e7c9f3ff 1217 }
99c1f5ab 1218
e7c9f3ff
NS
1219 return 0;
1220}
1221
1222static int start_devices(void)
1223{
1224 struct device_information *dip;
1225 int i, j, size;
1226
1227 size = ncpus * sizeof(struct thread_information);
1228 thread_information = malloc(size * ndevs);
1229 if (!thread_information) {
1230 fprintf(stderr, "Out of memory, threads (%d)\n", size * ndevs);
1231 return 1;
1232 }
24c7e97e 1233 memset(thread_information, 0, size * ndevs);
d5396421 1234
99c1f5ab 1235 for_each_dip(dip, i) {
e7c9f3ff
NS
1236 if (start_trace(dip)) {
1237 close(dip->fd);
1238 fprintf(stderr, "Failed to start trace on %s\n",
1239 dip->path);
1240 break;
1241 }
1242 }
99c1f5ab 1243
e7c9f3ff 1244 if (i != ndevs) {
ce020676 1245 __for_each_dip(dip, device_information, i, j)
e7c9f3ff 1246 stop_trace(dip);
99c1f5ab 1247
e7c9f3ff
NS
1248 return 1;
1249 }
1250
99c1f5ab 1251 for_each_dip(dip, i) {
e7c9f3ff
NS
1252 dip->threads = thread_information + (i * ncpus);
1253 if (start_threads(dip)) {
1254 fprintf(stderr, "Failed to start worker threads\n");
1255 break;
1256 }
1257 }
99c1f5ab 1258
e7c9f3ff 1259 if (i != ndevs) {
ce020676 1260 __for_each_dip(dip, device_information, i, j)
e7c9f3ff 1261 stop_threads(dip);
99c1f5ab 1262 for_each_dip(dip, i)
e7c9f3ff 1263 stop_trace(dip);
99c1f5ab 1264
e7c9f3ff 1265 return 1;
d0ca268b
JA
1266 }
1267
e7c9f3ff 1268 return 0;
d0ca268b
JA
1269}
1270
e0a1988b 1271static void show_stats(struct device_information *dips, int ndips, int cpus)
e7c9f3ff 1272{
e7c9f3ff
NS
1273 struct device_information *dip;
1274 struct thread_information *tip;
b7106311 1275 unsigned long long events_processed, data_read;
eb3c8108 1276 unsigned long total_drops;
2f903295 1277 int i, j, no_stdout = 0;
eb3c8108
JA
1278
1279 if (is_stat_shown())
1280 return;
1281
2f903295
JA
1282 if (output_name && !strcmp(output_name, "-"))
1283 no_stdout = 1;
1284
eb3c8108 1285 stat_shown = 1;
428683db 1286
56070ea4 1287 total_drops = 0;
ce020676 1288 __for_each_dip(dip, dips, ndips, i) {
2f903295 1289 if (!no_stdout)
56070ea4 1290 printf("Device: %s\n", dip->path);
e7c9f3ff 1291 events_processed = 0;
b7106311 1292 data_read = 0;
ce020676 1293 __for_each_tip(dip, tip, cpus, j) {
2f903295 1294 if (!no_stdout)
b7106311
JA
1295 printf(" CPU%3d: %20lu events, %8llu KiB data\n",
1296 tip->cpu, tip->events_processed,
54824c20 1297 (tip->data_read + 1023) >> 10);
e7c9f3ff 1298 events_processed += tip->events_processed;
b7106311 1299 data_read += tip->data_read;
e7c9f3ff 1300 }
eb3c8108 1301 total_drops += dip->drop_count;
2f903295 1302 if (!no_stdout)
b7106311
JA
1303 printf(" Total: %20llu events (dropped %lu), %8llu KiB data\n",
1304 events_processed, dip->drop_count,
18d8437d 1305 (data_read + 1023) >> 10);
e7c9f3ff 1306 }
56070ea4
JA
1307
1308 if (total_drops)
1309 fprintf(stderr, "You have dropped events, consider using a larger buffer size (-b)\n");
e7c9f3ff 1310}
52724a0e 1311
e0a1988b 1312static struct device_information *net_get_dip(struct net_connection *nc,
6a6d3f0f 1313 struct blktrace_net_hdr *bnh)
8e86c98a 1314{
ff11d54c
TZ
1315 struct device_information *dip, *cl_dip = NULL;
1316 struct cl_host *ch = nc->ch;
8e86c98a
JA
1317 int i;
1318
ff11d54c
TZ
1319 for (i = 0; i < ch->ndevs; i++) {
1320 dip = &ch->device_information[i];
8e86c98a 1321
6a6d3f0f 1322 if (!strcmp(dip->buts_name, bnh->buts_name))
22cd0c02 1323 return dip;
ff11d54c 1324
6a6d3f0f 1325 if (dip->cl_id == bnh->cl_id)
ff11d54c 1326 cl_dip = dip;
8e86c98a
JA
1327 }
1328
ff11d54c
TZ
1329 ch->device_information = realloc(ch->device_information, (ch->ndevs + 1) * sizeof(*dip));
1330 dip = &ch->device_information[ch->ndevs];
921b05fe
JA
1331 memset(dip, 0, sizeof(*dip));
1332 dip->fd = -1;
ff11d54c 1333 dip->ch = ch;
6a6d3f0f
TZ
1334 dip->cl_id = bnh->cl_id;
1335 dip->buf_size = bnh->buf_size;
1336 dip->buf_nr = bnh->buf_nr;
1337 dip->page_size = bnh->page_size;
1338
ff11d54c
TZ
1339 if (cl_dip)
1340 dip->cl_connect_time = cl_dip->cl_connect_time;
1341 else
1342 dip->cl_connect_time = nc->connect_time;
6a6d3f0f
TZ
1343 strcpy(dip->buts_name, bnh->buts_name);
1344 dip->path = strdup(bnh->buts_name);
7ab2f837 1345 dip->trace_started = 1;
ff11d54c 1346 ch->ndevs++;
e0a1988b
JA
1347 dip->threads = malloc(nc->ncpus * sizeof(struct thread_information));
1348 memset(dip->threads, 0, nc->ncpus * sizeof(struct thread_information));
22cd0c02
JA
1349
1350 /*
1351 * open all files
1352 */
e0a1988b 1353 for (i = 0; i < nc->ncpus; i++) {
22cd0c02 1354 struct thread_information *tip = &dip->threads[i];
8e86c98a 1355
22cd0c02 1356 tip->cpu = i;
22cd0c02 1357 tip->device = dip;
1366e53a 1358 tip->fd = -1;
ff11d54c
TZ
1359 tip->nc = nc;
1360
ddf22842 1361 if (tip_open_output(dip, tip))
22cd0c02 1362 return NULL;
ff11d54c
TZ
1363
1364 tip->nc = NULL;
8e86c98a
JA
1365 }
1366
22cd0c02
JA
1367 return dip;
1368}
1369
e0a1988b
JA
1370static struct thread_information *net_get_tip(struct net_connection *nc,
1371 struct blktrace_net_hdr *bnh)
22cd0c02
JA
1372{
1373 struct device_information *dip;
ff11d54c 1374 struct thread_information *tip;
22cd0c02 1375
6a6d3f0f 1376 dip = net_get_dip(nc, bnh);
7ab2f837
JA
1377 if (!dip->trace_started) {
1378 fprintf(stderr, "Events for closed devices %s\n", dip->buts_name);
1379 return NULL;
1380 }
1381
ff11d54c
TZ
1382 tip = &dip->threads[bnh->cpu];
1383 if (!tip->nc)
1384 tip->nc = nc;
1385
1386 return tip;
8e86c98a
JA
1387}
1388
e0a1988b
JA
1389static int net_get_header(struct net_connection *nc,
1390 struct blktrace_net_hdr *bnh)
8e86c98a 1391{
e0a1988b 1392 int fl = fcntl(nc->in_fd, F_GETFL);
8e86c98a
JA
1393 int bytes_left, ret;
1394 void *p = bnh;
1395
e0a1988b 1396 fcntl(nc->in_fd, F_SETFL, fl | O_NONBLOCK);
8e86c98a
JA
1397 bytes_left = sizeof(*bnh);
1398 while (bytes_left && !is_done()) {
e0a1988b 1399 ret = recv(nc->in_fd, p, bytes_left, MSG_WAITALL);
8e86c98a
JA
1400 if (ret < 0) {
1401 if (errno != EAGAIN) {
1402 perror("recv header");
1403 return 1;
1404 }
7934e668 1405 usleep(1000);
8e86c98a
JA
1406 continue;
1407 } else if (!ret) {
7934e668 1408 usleep(1000);
8e86c98a
JA
1409 continue;
1410 } else {
1411 p += ret;
1412 bytes_left -= ret;
1413 }
1414 }
e0a1988b 1415 fcntl(nc->in_fd, F_SETFL, fl & ~O_NONBLOCK);
227f89ff 1416 return bytes_left;
8e86c98a
JA
1417}
1418
e0a1988b
JA
1419/*
1420 * finalize a net client: truncate files, show stats, cleanup, etc
1421 */
ff11d54c 1422static void device_done(struct net_connection *nc, struct device_information *dip)
e0a1988b 1423{
e0a1988b 1424 struct thread_information *tip;
ff11d54c 1425 int i;
e0a1988b 1426
ff11d54c
TZ
1427 __for_each_tip(dip, tip, nc->ncpus, i)
1428 tip_ftrunc_final(tip);
e0a1988b 1429
ff11d54c 1430 show_stats(dip, 1, nc->ncpus);
e0a1988b
JA
1431
1432 /*
1433 * cleanup for next run
1434 */
ff11d54c
TZ
1435 __for_each_tip(dip, tip, nc->ncpus, i) {
1436 if (tip->ofile)
1437 fclose(tip->ofile);
e0a1988b
JA
1438 }
1439
ff11d54c
TZ
1440 free(dip->threads);
1441 free(dip->path);
e0a1988b
JA
1442
1443 close(nc->in_fd);
1444 nc->in_fd = -1;
1445
ff11d54c
TZ
1446 stat_shown = 0;
1447}
e0a1988b 1448
ff11d54c
TZ
1449static inline int in_addr_eq(struct in_addr a, struct in_addr b)
1450{
1451 return a.s_addr == b.s_addr;
1452}
1453
1454static void net_add_client_host(struct cl_host *ch)
1455{
1456 ch->list_next = cl_host_list;
1457 cl_host_list = ch;
1458 cl_hosts++;
1459}
1460
1461static void net_remove_client_host(struct cl_host *ch)
1462{
1463 struct cl_host *p, *c;
1464
1465 for (p = c = cl_host_list; c; c = c->list_next) {
1466 if (c == ch) {
1467 if (p == c)
1468 cl_host_list = c->list_next;
1469 else
1470 p->list_next = c->list_next;
1471 cl_hosts--;
1472 return;
1473 }
1474 p = c;
e0a1988b 1475 }
ff11d54c 1476}
e0a1988b 1477
ff11d54c
TZ
1478static struct cl_host *net_find_client_host(struct in_addr cl_in_addr)
1479{
1480 struct cl_host *ch = cl_host_list;
1481
1482 while (ch) {
1483 if (in_addr_eq(ch->cl_in_addr, cl_in_addr))
1484 return ch;
1485 ch = ch->list_next;
1486 }
1487
1488 return NULL;
1489}
1490
ff11d54c
TZ
1491static void net_client_host_done(struct cl_host *ch)
1492{
1493 free(ch->device_information);
1494 free(ch->net_connections);
1495 net_connects -= ch->nconn;
1496 net_remove_client_host(ch);
1497 free(ch);
e0a1988b
JA
1498}
1499
1500/*
1501 * handle incoming events from a net client
1502 */
1503static int net_client_data(struct net_connection *nc)
8e86c98a
JA
1504{
1505 struct thread_information *tip;
1506 struct blktrace_net_hdr bnh;
1507
e0a1988b 1508 if (net_get_header(nc, &bnh))
8e86c98a
JA
1509 return 1;
1510
1511 if (data_is_native == -1 && check_data_endianness(bnh.magic)) {
1512 fprintf(stderr, "server: received data is bad\n");
1513 return 1;
1514 }
1515
1516 if (!data_is_native) {
227f89ff 1517 bnh.magic = be32_to_cpu(bnh.magic);
8e86c98a 1518 bnh.cpu = be32_to_cpu(bnh.cpu);
4aeec019 1519 bnh.max_cpus = be32_to_cpu(bnh.max_cpus);
8e86c98a 1520 bnh.len = be32_to_cpu(bnh.len);
ff11d54c 1521 bnh.cl_id = be32_to_cpu(bnh.cl_id);
6a6d3f0f
TZ
1522 bnh.buf_size = be32_to_cpu(bnh.buf_size);
1523 bnh.buf_nr = be32_to_cpu(bnh.buf_nr);
1524 bnh.page_size = be32_to_cpu(bnh.page_size);
8e86c98a
JA
1525 }
1526
227f89ff
JA
1527 if ((bnh.magic & 0xffffff00) != BLK_IO_TRACE_MAGIC) {
1528 fprintf(stderr, "server: bad data magic\n");
1529 return 1;
1530 }
1531
4aeec019
JA
1532 if (nc->ncpus == -1)
1533 nc->ncpus = bnh.max_cpus;
1534
6a752c90
JA
1535 /*
1536 * len == 0 means that the other end signalled end-of-run
1537 */
1538 if (!bnh.len) {
7934e668
JA
1539 /*
1540 * overload cpu count with dropped events
1541 */
1542 struct device_information *dip;
1543
6a6d3f0f 1544 dip = net_get_dip(nc, &bnh);
7934e668 1545 dip->drop_count = bnh.cpu;
7ab2f837 1546 dip->trace_started = 0;
7934e668 1547
e0a1988b 1548 printf("server: end of run for %s\n", dip->buts_name);
4aeec019 1549
ff11d54c
TZ
1550 device_done(nc, dip);
1551
1552 if (++nc->ch->ndevs_done == nc->ch->ndevs)
1553 net_client_host_done(nc->ch);
4aeec019 1554
e0a1988b 1555 return 0;
6a752c90
JA
1556 }
1557
e0a1988b 1558 tip = net_get_tip(nc, &bnh);
8e86c98a
JA
1559 if (!tip)
1560 return 1;
1561
1562 if (mmap_subbuf(tip, bnh.len))
1563 return 1;
1564
1565 return 0;
1566}
1567
e0a1988b 1568static void net_add_connection(int listen_fd, struct sockaddr_in *addr)
659bcc3f 1569{
e0a1988b
JA
1570 socklen_t socklen = sizeof(*addr);
1571 struct net_connection *nc;
ff11d54c
TZ
1572 struct cl_host *ch;
1573 int in_fd;
659bcc3f 1574
ff11d54c
TZ
1575 in_fd = accept(listen_fd, (struct sockaddr *) addr, &socklen);
1576 if (in_fd < 0) {
1577 perror("accept");
e0a1988b 1578 return;
659bcc3f 1579 }
659bcc3f 1580
ff11d54c
TZ
1581 ch = net_find_client_host(addr->sin_addr);
1582 if (!ch) {
1583 if (cl_hosts == NET_MAX_CL_HOSTS) {
1584 fprintf(stderr, "server: no more clients allowed\n");
1585 return;
1586 }
1587 ch = malloc(sizeof(struct cl_host));
1588 memset(ch, 0, sizeof(*ch));
1589 ch->cl_in_addr = addr->sin_addr;
1590 net_add_client_host(ch);
506cdb6d
JA
1591
1592 printf("server: connection from %s\n", inet_ntoa(addr->sin_addr));
659bcc3f
JA
1593 }
1594
ff11d54c
TZ
1595 ch->net_connections = realloc(ch->net_connections, (ch->nconn + 1) * sizeof(*nc));
1596 nc = &ch->net_connections[ch->nconn++];
1597 memset(nc, 0, sizeof(*nc));
1598
e0a1988b 1599 time(&nc->connect_time);
ff11d54c
TZ
1600 nc->ch = ch;
1601 nc->in_fd = in_fd;
4aeec019 1602 nc->ncpus = -1;
e0a1988b
JA
1603 net_connects++;
1604}
1605
1606/*
1607 * event driven loop, handle new incoming connections and data from
1608 * existing connections
1609 */
1610static void net_server_handle_connections(int listen_fd,
1611 struct sockaddr_in *addr)
1612{
ff11d54c
TZ
1613 struct pollfd *pfds = NULL;
1614 struct net_connection **ncs = NULL;
1615 int max_connects = 0;
1616 int i, nconns, events;
1617 struct cl_host *ch;
1618 struct net_connection *nc;
1619
e0a1988b
JA
1620 printf("server: waiting for connections...\n");
1621
1622 while (!is_done()) {
ff11d54c
TZ
1623 if (net_connects >= max_connects) {
1624 pfds = realloc(pfds, (net_connects + 1) * sizeof(*pfds));
1625 ncs = realloc(ncs, (net_connects + 1) * sizeof(*ncs));
1626 max_connects = net_connects + 1;
1627 }
e0a1988b
JA
1628 /*
1629 * the zero entry is for incoming connections, remaining
1630 * entries for clients
1631 */
1632 pfds[0].fd = listen_fd;
1633 pfds[0].events = POLLIN;
ff11d54c
TZ
1634 nconns = 0;
1635 for_each_cl_host(ch) {
1636 for (i = 0; i < ch->nconn; i++) {
1637 nc = &ch->net_connections[i];
1638 pfds[nconns + 1].fd = nc->in_fd;
1639 pfds[nconns + 1].events = POLLIN;
1640 ncs[nconns++] = nc;
1641 }
e0a1988b
JA
1642 }
1643
ff11d54c 1644 events = poll(pfds, 1 + nconns, -1);
e0a1988b
JA
1645 if (events < 0) {
1646 if (errno == EINTR)
1647 continue;
1648
1649 perror("poll");
1650 break;
1651 } else if (!events)
1652 continue;
1653
1654 if (pfds[0].revents & POLLIN) {
1655 net_add_connection(listen_fd, addr);
1656 events--;
1657 }
1658
ff11d54c 1659 for (i = 0; events && i < nconns; i++) {
e0a1988b 1660 if (pfds[i + 1].revents & POLLIN) {
ff11d54c 1661 net_client_data(ncs[i]);
e0a1988b
JA
1662 events--;
1663 }
1664 }
1665 }
659bcc3f
JA
1666}
1667
8e86c98a
JA
1668/*
1669 * Start here when we are in server mode - just fetch data from the network
1670 * and dump to files
1671 */
1672static int net_server(void)
1673{
1674 struct sockaddr_in addr;
e0a1988b 1675 int fd, opt;
8e86c98a
JA
1676
1677 fd = socket(AF_INET, SOCK_STREAM, 0);
1678 if (fd < 0) {
1679 perror("server: socket");
1680 return 1;
1681 }
1682
1683 opt = 1;
1684 if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
1685 perror("setsockopt");
1686 return 1;
1687 }
1688
1689 memset(&addr, 0, sizeof(addr));
1690 addr.sin_family = AF_INET;
1691 addr.sin_addr.s_addr = htonl(INADDR_ANY);
1692 addr.sin_port = htons(net_port);
1693
1694 if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
1695 perror("bind");
1696 return 1;
1697 }
1698
1699 if (listen(fd, 1) < 0) {
1700 perror("listen");
1701 return 1;
1702 }
1703
e0a1988b
JA
1704 net_server_handle_connections(fd, &addr);
1705 return 0;
8e86c98a
JA
1706}
1707
1708/*
1709 * Setup outgoing network connection where we will transmit data
1710 */
ff11d54c 1711static int net_setup_client_cpu(int i, struct sockaddr_in *addr)
8e86c98a 1712{
8e86c98a
JA
1713 int fd;
1714
1715 fd = socket(AF_INET, SOCK_STREAM, 0);
1716 if (fd < 0) {
1717 perror("client: socket");
1718 return 1;
1719 }
1720
ff11d54c
TZ
1721 if (connect(fd, (struct sockaddr *) addr, sizeof(*addr)) < 0) {
1722 perror("client: connect");
1723 return 1;
1724 }
1725
1726 net_out_fd[i] = fd;
1727 return 0;
1728}
1729
1730static int net_setup_client(void)
1731{
1732 struct sockaddr_in addr;
1733 int i;
1734
8e86c98a
JA
1735 memset(&addr, 0, sizeof(addr));
1736 addr.sin_family = AF_INET;
1737 addr.sin_port = htons(net_port);
1738
1739 if (inet_aton(hostname, &addr.sin_addr) != 1) {
1740 struct hostent *hent = gethostbyname(hostname);
1741 if (!hent) {
1742 perror("gethostbyname");
1743 return 1;
1744 }
1745
1746 memcpy(&addr.sin_addr, hent->h_addr, 4);
1747 strcpy(hostname, hent->h_name);
1748 }
1749
1750 printf("blktrace: connecting to %s\n", hostname);
1751
ff11d54c
TZ
1752 net_out_fd = malloc(ncpus * sizeof(*net_out_fd));
1753 for (i = 0; i < ncpus; i++) {
1754 if (net_setup_client_cpu(i, &addr))
1755 return 1;
8e86c98a
JA
1756 }
1757
1758 printf("blktrace: connected!\n");
ff11d54c 1759
8e86c98a
JA
1760 return 0;
1761}
1762
52724a0e 1763static char usage_str[] = \
3d06efea 1764 "-d <dev> [ -r debugfs path ] [ -o <output> ] [-k ] [ -w time ]\n" \
cf1edb17 1765 "[ -a action ] [ -A action mask ] [ -I <devs file> ] [ -v ]\n\n" \
52724a0e 1766 "\t-d Use specified device. May also be given last after options\n" \
90ec4b15 1767 "\t-r Path to mounted debugfs, defaults to /sys/kernel/debug\n" \
52724a0e 1768 "\t-o File(s) to send output to\n" \
d1d7f15f 1769 "\t-D Directory to prepend to output file names\n" \
52724a0e
JA
1770 "\t-k Kill a running trace\n" \
1771 "\t-w Stop after defined time, in seconds\n" \
1772 "\t-a Only trace specified actions. See documentation\n" \
1773 "\t-A Give trace mask as a single value. See documentation\n" \
129aa440
JA
1774 "\t-b Sub buffer size in KiB\n" \
1775 "\t-n Number of sub buffers\n" \
f531b94d
JA
1776 "\t-l Run in network listen mode (blktrace server)\n" \
1777 "\t-h Run in network client mode, connecting to the given host\n" \
1778 "\t-p Network port to use (default 8462)\n" \
ff11d54c 1779 "\t-s Make the network client NOT use sendfile() to transfer data\n" \
cf1edb17 1780 "\t-I Add devices found in <devs file>\n" \
f531b94d 1781 "\t-V Print program version info\n\n";
52724a0e 1782
ee1f4158
NS
1783static void show_usage(char *program)
1784{
52724a0e 1785 fprintf(stderr, "Usage: %s %s %s",program, blktrace_version, usage_str);
ee1f4158 1786}
d0ca268b
JA
1787
1788int main(int argc, char *argv[])
1789{
c76adfc1 1790 static char default_debugfs_path[] = "/sys/kernel/debug";
e3e74029 1791 struct statfs st;
d39c04ca 1792 int i, c;
ece238a6 1793 int stop_watch = 0;
d39c04ca
AB
1794 int act_mask_tmp = 0;
1795
1796 while ((c = getopt_long(argc, argv, S_OPTS, l_opts, NULL)) >= 0) {
1797 switch (c) {
1798 case 'a':
1799 i = find_mask_map(optarg);
1800 if (i < 0) {
ab197ca7 1801 fprintf(stderr,"Invalid action mask %s\n",
d39c04ca 1802 optarg);
7425d456 1803 return 1;
d39c04ca
AB
1804 }
1805 act_mask_tmp |= i;
1806 break;
1807
1808 case 'A':
98f8386b
AB
1809 if ((sscanf(optarg, "%x", &i) != 1) ||
1810 !valid_act_opt(i)) {
d39c04ca 1811 fprintf(stderr,
ab197ca7 1812 "Invalid set action mask %s/0x%x\n",
d39c04ca 1813 optarg, i);
7425d456 1814 return 1;
d39c04ca
AB
1815 }
1816 act_mask_tmp = i;
1817 break;
d0ca268b 1818
d39c04ca 1819 case 'd':
e7c9f3ff
NS
1820 if (resize_devices(optarg) != 0)
1821 return 1;
d39c04ca
AB
1822 break;
1823
cf1edb17
AB
1824 case 'I': {
1825 char dev_line[256];
1826 FILE *ifp = fopen(optarg, "r");
1827
1828 if (!ifp) {
1829 fprintf(stderr,
1830 "Invalid file for devices %s\n",
1831 optarg);
1832 return 1;
1833 }
1834
1835 while (fscanf(ifp, "%s\n", dev_line) == 1)
1836 if (resize_devices(strdup(dev_line)) != 0)
1837 return 1;
1838 break;
1839 }
1840
1841
5270dddd 1842 case 'r':
3d06efea 1843 debugfs_path = optarg;
5270dddd
JA
1844 break;
1845
d5396421 1846 case 'o':
66efebf8 1847 output_name = optarg;
d5396421 1848 break;
bc39777c
JA
1849 case 'k':
1850 kill_running_trace = 1;
1851 break;
ece238a6
NS
1852 case 'w':
1853 stop_watch = atoi(optarg);
1854 if (stop_watch <= 0) {
1855 fprintf(stderr,
1856 "Invalid stopwatch value (%d secs)\n",
1857 stop_watch);
1858 return 1;
1859 }
1860 break;
57ea8602 1861 case 'V':
5d4f19d9 1862 case 'v':
52724a0e
JA
1863 printf("%s version %s\n", argv[0], blktrace_version);
1864 return 0;
129aa440 1865 case 'b':
eb3c8108 1866 buf_size = strtoul(optarg, NULL, 10);
183a0855 1867 if (buf_size <= 0 || buf_size > 16*1024) {
129aa440 1868 fprintf(stderr,
eb3c8108 1869 "Invalid buffer size (%lu)\n",buf_size);
129aa440
JA
1870 return 1;
1871 }
1872 buf_size <<= 10;
1873 break;
1874 case 'n':
eb3c8108 1875 buf_nr = strtoul(optarg, NULL, 10);
129aa440
JA
1876 if (buf_nr <= 0) {
1877 fprintf(stderr,
eb3c8108 1878 "Invalid buffer nr (%lu)\n", buf_nr);
129aa440
JA
1879 return 1;
1880 }
1881 break;
d1d7f15f
JA
1882 case 'D':
1883 output_dir = optarg;
1884 break;
8e86c98a
JA
1885 case 'h':
1886 net_mode = Net_client;
1887 strcpy(hostname, optarg);
1888 break;
1889 case 'l':
1890 net_mode = Net_server;
1891 break;
1892 case 'p':
1893 net_port = atoi(optarg);
1894 break;
32f18c48 1895 case 's':
79971f43 1896 net_use_sendfile = 0;
32f18c48 1897 break;
d39c04ca 1898 default:
ee1f4158 1899 show_usage(argv[0]);
7425d456 1900 return 1;
d39c04ca
AB
1901 }
1902 }
1903
8e86c98a
JA
1904 setlocale(LC_NUMERIC, "en_US");
1905
1906 page_size = getpagesize();
1907
ec685dd2
JA
1908 if (net_mode == Net_server) {
1909 if (output_name) {
1910 fprintf(stderr, "-o ignored in server mode\n");
1911 output_name = NULL;
1912 }
1913
8e86c98a 1914 return net_server();
ec685dd2 1915 }
8e86c98a 1916
22cd0c02
JA
1917 while (optind < argc) {
1918 if (resize_devices(argv[optind++]) != 0)
1919 return 1;
1920 }
1921
e7c9f3ff 1922 if (ndevs == 0) {
ee1f4158 1923 show_usage(argv[0]);
7425d456 1924 return 1;
d39c04ca
AB
1925 }
1926
d5396421 1927 if (act_mask_tmp != 0)
d39c04ca 1928 act_mask = act_mask_tmp;
d0ca268b 1929
3d06efea
JA
1930 if (!debugfs_path)
1931 debugfs_path = default_debugfs_path;
1932
1933 if (statfs(debugfs_path, &st) < 0) {
e3e74029
NS
1934 perror("statfs");
1935 fprintf(stderr,"%s does not appear to be a valid path\n",
3d06efea 1936 debugfs_path);
e3e74029 1937 return 1;
3d06efea
JA
1938 } else if (st.f_type != (long) DEBUGFS_TYPE) {
1939 fprintf(stderr,"%s does not appear to be a debug filesystem\n",
1940 debugfs_path);
7425d456 1941 return 1;
d0ca268b
JA
1942 }
1943
e7c9f3ff 1944 if (open_devices() != 0)
7425d456 1945 return 1;
bc39777c
JA
1946
1947 if (kill_running_trace) {
e7c9f3ff 1948 stop_all_traces();
7425d456 1949 return 0;
bc39777c
JA
1950 }
1951
e7c9f3ff
NS
1952 ncpus = sysconf(_SC_NPROCESSORS_ONLN);
1953 if (ncpus < 0) {
1954 fprintf(stderr, "sysconf(_SC_NPROCESSORS_ONLN) failed\n");
7425d456 1955 return 1;
d0ca268b
JA
1956 }
1957
d0ca268b
JA
1958 signal(SIGINT, handle_sigint);
1959 signal(SIGHUP, handle_sigint);
1960 signal(SIGTERM, handle_sigint);
ece238a6 1961 signal(SIGALRM, handle_sigint);
38e1f0c6 1962 signal(SIGPIPE, SIG_IGN);
d0ca268b 1963
8e86c98a
JA
1964 if (net_mode == Net_client && net_setup_client())
1965 return 1;
1966
1967 if (start_devices() != 0)
1968 return 1;
1969
e7c9f3ff 1970 atexit(stop_all_tracing);
830fd65c 1971
ece238a6
NS
1972 if (stop_watch)
1973 alarm(stop_watch);
1974
b7106311 1975 wait_for_threads();
d0ca268b 1976
eb3c8108
JA
1977 if (!is_trace_stopped()) {
1978 trace_stopped = 1;
91816d54
JA
1979 stop_all_threads();
1980 stop_all_traces();
91816d54 1981 }
d0ca268b 1982
e0a1988b 1983 show_stats(device_information, ndevs, ncpus);
eb3c8108 1984
d0ca268b
JA
1985 return 0;
1986}
1987