X-Git-Url: http://rtime.felk.cvut.cz/gitweb/can-benchmark.git/blobdiff_plain/06d101244ce0c2ed3de28a95ad54d45b8e7e2586..bc6e060953d52e4aa7e57954f2833168e9d8d795:/latester/latester.c diff --git a/latester/latester.c b/latester/latester.c index 774e9d9..f58086a 100644 --- a/latester/latester.c +++ b/latester/latester.c @@ -1,6 +1,6 @@ /**************************************************************************/ /* CAN latency tester */ -/* Copyright (C) 2010 Michal Sojka, DCE FEE CTU Prague */ +/* Copyright (C) 2010, 2011 Michal Sojka, DCE FEE CTU Prague */ /* License: GPLv2 */ /**************************************************************************/ @@ -10,8 +10,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -19,19 +22,21 @@ #include #include #include +#include +#include +#include #include #include -//#include +#include #include -#include -#include -#include + #include #include -#include #include "histogram.h" +//#define FTRACE + #ifndef DEBUG #define dbg(level, fmt, arg...) do {} while (0) #else @@ -46,13 +51,45 @@ volatile sig_atomic_t finish_flag = 0; /* Threads should terminate. */ sem_t finish_sem; /* Thread signals a termination */ -#define MAX_INTERFACES 4 - /* Command line options */ -char *option_interface[MAX_INTERFACES]; +struct options { + char **interface; + canid_t id; + unsigned period_us; + unsigned timeout_ms; + unsigned count; + unsigned oneattime; + char *name; + int length; + int userhist; + int quiet; + + /* Temporary variables */ + FILE *f_msgs; + FILE *f_hist; + FILE *f_stat; +}; + +struct options opt = { + .id = 10, + .period_us = 0, + .timeout_ms = 1000, + .length = 2, +}; + +struct { + unsigned enobufs; + unsigned overrun; + unsigned lost; + struct timespec tic, tac; + unsigned timeouts; + unsigned invalid_frame; +} stats; + int num_interfaces = 0; -canid_t option_id; -unsigned option_period_us = 100000; +int count = 0; /* Number of sent messages */ +unsigned msg_in_progress = 0; +int completion_pipe[2]; struct msg_info { canid_t id; @@ -60,47 +97,192 @@ struct msg_info { struct timespec ts_sent, ts_sent_kern; struct timespec ts_rx_onwire, ts_rx_onwire_kern; struct timespec ts_rx_final, ts_rx_final_kern; + struct can_frame sent, received; }; #define MAX_INFOS 10000 struct msg_info msg_infos[MAX_INFOS]; -uint16_t curr_msg = 0; -static inline struct msg_info *frame2info(struct can_frame *frame) +struct histogram histogram; + +void sprint_canframe(char *buf , struct can_frame *cf, int sep) { + /* documentation see lib.h */ + + int i,offset; + int dlc = (cf->can_dlc > 8)? 8 : cf->can_dlc; + + if (cf->can_id & CAN_ERR_FLAG) { + sprintf(buf, "%08X#", cf->can_id & (CAN_ERR_MASK|CAN_ERR_FLAG)); + offset = 9; + } else if (cf->can_id & CAN_EFF_FLAG) { + sprintf(buf, "%08X#", cf->can_id & CAN_EFF_MASK); + offset = 9; + } else { + sprintf(buf, "%03X#", cf->can_id & CAN_SFF_MASK); + offset = 4; + } + + if (cf->can_id & CAN_RTR_FLAG) /* there are no ERR frames with RTR */ + sprintf(buf+offset, "R"); + else + for (i = 0; i < dlc; i++) { + sprintf(buf+offset, "%02X", cf->data[i]); + offset += 2; + if (sep && (i+1 < dlc)) + sprintf(buf+offset++, "."); + } +} + +static inline uint16_t frame_index(struct can_frame *frame) { uint16_t idx; - if (frame->can_dlc == 2) { + if (frame->can_dlc >= 2) { memcpy(&idx, frame->data, sizeof(idx)); if (idx >= MAX_INFOS) error(1, 0, "%s idx too high", __FUNCTION__); - } else + } else { + error(1, 0, "%s error", __FUNCTION__); - return &msg_infos[idx]; + } + return idx; } -static inline char *tstamp_str(char *buf, struct timespec *tstamp) +static inline struct msg_info *frame2info(struct can_frame *frame) { - sprintf(buf, "%ld.%06ld", tstamp->tv_sec, tstamp->tv_nsec/1000); + return &msg_infos[frame_index(frame)]; } -void print_msg_info(struct msg_info *mi) +static inline char *tstamp_str(const void *ctx, struct timespec *tstamp) { - struct timespec ts_diff1, ts_diff2; - char str_sent[32], str_kern[32], str_user[32], str_diff1[32], str_diff2[32]; - timespec_subtract(&ts_diff1, &mi->ts_rx_final_kern, &mi->ts_sent); - timespec_subtract(&ts_diff2, &mi->ts_rx_final, &mi->ts_sent); - tstamp_str(str_sent, &mi->ts_sent); - tstamp_str(str_kern, &mi->ts_rx_final_kern); - tstamp_str(str_user, &mi->ts_rx_final); - tstamp_str(str_diff1, &ts_diff1); - tstamp_str(str_diff2, &ts_diff2); - printf("%s -> %s (%s) = %s (%s)\n", str_sent, str_user, str_kern, str_diff1, str_diff2); + return talloc_asprintf(ctx, "%ld.%06ld", + tstamp->tv_sec, tstamp->tv_nsec/1000); +} + +unsigned calc_stuff_bits(struct can_frame *frame) { + uint32_t bitmap[4]; + unsigned start = 0, end; + uint32_t mask, ones = 0, basemask = 0xf0000000; + unsigned stuffed = 0; + memset(bitmap, 0, sizeof(bitmap)); + + if (frame->can_id & CAN_EFF_FLAG) { + bitmap[0] = + (frame->can_id >> 25); + bitmap[1] = + (frame->can_id >> 18) << 25 | + 3 << 23 | + (frame->can_id & 0x3ffff) << 7 | + (!!(frame->can_id & CAN_RTR_FLAG)) << 6 | + 0 << 4 | + frame->can_dlc & 0xf; + bitmap[2] = htonl(((uint32_t*)frame->data)[0]); + bitmap[3] = htonl(((uint32_t*)frame->data)[1]); + start = 28; + end = 64 + 8*frame->can_dlc; + } else { + bitmap[0] = + (frame->can_id << 7) | + (!!(frame->can_id & CAN_RTR_FLAG)) << 6 | + 0 << 4 | + frame->can_dlc & 0xf; + bitmap[1] = htonl(((uint32_t*)frame->data)[0]); + bitmap[2] = htonl(((uint32_t*)frame->data)[1]); + start = 14; + end = 32 + 8*frame->can_dlc; + } + /* TODO: Calc stuff bits in CRC */ + while (start < end) { + mask = basemask >> (start & 0x1f); + while (1) { + ones = ones ? mask : 0; + uint32_t chunk = (bitmap[start >> 5] & mask) ^ ones; + //printf("start=%d bitmap=0x%08x mask=0x%08x ones=0x%08x chunk=0x%08x\n", start, bitmap[start >> 5], mask, ones, chunk); + if (chunk) { + unsigned change = __builtin_clz(chunk); + start = start & ~0x1f | change; + basemask = 0xf8000000; + } else { + unsigned oldstart = start; + start += __builtin_popcount(mask); + mask = (oldstart & 0x1f) ? basemask << (-oldstart & 0x1f) : 0; + //printf("oldstart=%d shl=%d mask=0x%08x\n", oldstart, -oldstart & 0x1f, mask); + if (mask && start < end) + continue; + if (start <= end && !mask) { + stuffed++; + basemask = 0xf0000000; + //printf("stuffed %d\n", !ones); + } + } + break; + } + ones = !ones; + } + //printf ("STUFFED %d BITS\n", stuffed); } +unsigned calc_frame_txtime(struct can_frame *frame) { + return calc_stuff_bits(frame) + + 1 + /* SOF */ + 11 + /* ID A */ + ((frame->can_id & CAN_EFF_FLAG) ? + 1 + /* SRR */ + 1 + /* IDE */ + 18 + /* ID B */ + 1 + /* RTR */ + 2 /* r1, r0 */ + : + 1 + /* rtr */ + 2) + /* ide, r0 */ + 4 + /* dlc */ + 8*frame->can_dlc + + 15 + /* CRC */ + 3 + /* CRC del, ACK, ACK del */ + 7; /* EOF */ +} + +void msg_info_print(FILE *f, struct msg_info *mi) +{ + struct timespec diff; + void *local = talloc_new (NULL); + static long num = 0; + char sent[64], received[64]; + + sprint_canframe(sent, &mi->sent, true); + sprint_canframe(received, &mi->received, true); + +#define S(ts) tstamp_str(local, &ts) +#define DIFF(a, b) (timespec_subtract(&diff, &b, &a), S(diff)) + + switch (num_interfaces) { + case 2: + fprintf(f, "%ld: %s %s -> %s (%s) %s = %s (%s) %d\n", + num, S(mi->ts_sent), sent, S(mi->ts_rx_final_kern), S(mi->ts_rx_final), received, + DIFF(mi->ts_sent, mi->ts_rx_final_kern), + DIFF(mi->ts_sent, mi->ts_rx_final), + calc_frame_txtime(&mi->received)); + break; + case 3: + fprintf(f, "%ld: %s %s -> %s (%s) -> %s (%s) %s = %s (%s), %s (%s) %d\n", + num, S(mi->ts_sent), sent, + S(mi->ts_rx_onwire_kern), S(mi->ts_rx_onwire), + S(mi->ts_rx_final_kern), S(mi->ts_rx_final), received, + DIFF(mi->ts_sent, mi->ts_rx_onwire_kern), + DIFF(mi->ts_sent, mi->ts_rx_onwire), + DIFF(mi->ts_rx_onwire_kern, mi->ts_rx_final_kern), + DIFF(mi->ts_rx_onwire, mi->ts_rx_final), + calc_frame_txtime(&mi->received)); + break; + } +#undef S +#undef DIFF + num++; + talloc_free (local); +} /* Subtract the `struct timespec' values X and Y, storing the result in RESULT. Return 1 if the difference is negative, otherwise 0. */ - + int timespec_subtract (struct timespec *result, struct timespec *x, struct timespec *yy) { struct timespec ylocal = *yy, *y = &ylocal; @@ -115,12 +297,12 @@ int timespec_subtract (struct timespec *result, struct timespec *x, struct times y->tv_nsec += 1000000000 * nsec; y->tv_sec -= nsec; } - + /* Compute the time remaining to wait. `tv_nsec' is certainly positive. */ result->tv_sec = x->tv_sec - y->tv_sec; result->tv_nsec = x->tv_nsec - y->tv_nsec; - + /* Return 1 if result is negative. */ return x->tv_sec < y->tv_sec; } @@ -131,6 +313,28 @@ void dbg_print_timespec(char *msg, struct timespec *tv) printf("%s sec=%ld nsec=%ld\n", msg, tv->tv_sec, tv->tv_nsec); } +static inline unsigned get_msg_latency_us(struct msg_info *mi) +{ + struct timespec diff; + switch (num_interfaces) { + case 3: + if (opt.userhist) + timespec_subtract(&diff, &mi->ts_rx_final, &mi->ts_rx_onwire); + else + timespec_subtract(&diff, &mi->ts_rx_final_kern, &mi->ts_rx_onwire_kern); + break; + case 2: + if (opt.userhist) + timespec_subtract(&diff, &mi->ts_rx_final, &mi->ts_sent); + else + timespec_subtract(&diff, &mi->ts_rx_final_kern, &mi->ts_sent); + break; + default: + return 0; + } + return diff.tv_sec * 1000000 + diff.tv_nsec/1000; +} + void set_sched_policy_and_prio(int policy, int rtprio) { struct sched_param scheduling_parameters; @@ -156,10 +360,10 @@ static inline int sock_get_if_index(int s, const char *if_name) { struct ifreq ifr; MEMSET_ZERO(ifr); - + strcpy(ifr.ifr_name, if_name); if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) - error(1, errno, "SIOCGIFINDEX"); + error(1, errno, "SIOCGIFINDEX '%s'", if_name); return ifr.ifr_ifindex; } @@ -168,38 +372,153 @@ static inline get_tstamp(struct timespec *ts) clock_gettime(CLOCK_REALTIME, ts); } + +int trace_fd = -1; +int marker_fd = -1; + +int init_ftrace() +{ +#ifdef FTRACE + char *debugfs; + char path[256]; + FILE *f; + + debugfs = "/sys/kernel/debug"; + if (debugfs) { + strcpy(path, debugfs); + strcat(path,"/tracing/tracing_on"); + trace_fd = open(path, O_WRONLY); + if (trace_fd >= 0) + write(trace_fd, "1", 1); + + strcpy(path, debugfs); + strcat(path,"/tracing/trace_marker"); + marker_fd = open(path, O_WRONLY); + + strcpy(path, debugfs); + strcat(path,"/tracing/set_ftrace_pid"); + f = fopen(path, "w"); + fprintf(f, "%d\n", getpid()); + fclose(f); + system("echo function_graph > /sys/kernel/debug/tracing/current_tracer"); + system("echo can_send > /sys/kernel/debug/tracing/set_graph_function"); + system("echo > /sys/kernel/debug/tracing/trace"); + system("echo 1 > /sys/kernel/debug/tracing/tracing_enabled"); + } +#endif /* FTRACE */ +} + +static inline void trace_on() +{ + if (trace_fd >= 0) + write(trace_fd, "1", 1); +} + +static inline void trace_off(int ret) +{ + if (marker_fd >= 0) { + char marker[100]; + sprintf(marker, "write returned %d\n", ret); + write(marker_fd, marker, strlen(marker)); + } + if (trace_fd >= 0) + write(trace_fd, "0", 1); +} + +static inline void msg_info_free(struct msg_info *mi) +{ + mi->id = -1; +} + +static inline bool msg_info_used(struct msg_info *mi) +{ + return mi->id != -1; +} + int send_frame(int socket) { struct can_frame frame; struct msg_info *mi; int ret; + static int curr_msg = -1; + int i; + uint16_t idx; - frame.can_id = option_id; - frame.can_dlc = 2; - memcpy(frame.data, &curr_msg, sizeof(curr_msg)); + MEMSET_ZERO(frame); + i = curr_msg+1; + while (msg_info_used(&msg_infos[i]) && i != curr_msg) { + i++; + if (i >= MAX_INFOS) + i = 0; + } + if (i == curr_msg) + error(1, 0, "Msg info table is full! Probably, many packets were lost."); + else + curr_msg = i; + + frame.can_id = opt.id; + if (opt.length < 2) + error(1, 0, "Length < 2 is not yet supported"); + frame.can_dlc = opt.length; + idx = curr_msg; + memcpy(frame.data, &idx, sizeof(idx)); mi = frame2info(&frame); + mi->id = frame.can_id; + mi->length = frame.can_dlc; get_tstamp(&mi->ts_sent); + mi->sent = frame; + + trace_on(); ret = write(socket, &frame, sizeof(frame)); + trace_off(ret); + + if (ret == -1 || num_interfaces == 1) + msg_info_free(mi); return ret; } +static inline send_and_check(int s) +{ + int ret; + ret = send_frame(s); + if (ret != sizeof(struct can_frame)) { +/* if (ret == -1 && errno == ENOBUFS && opt.period_us == 0 && !opt.oneattime) { */ +/* stats.enobufs++; */ +/* /\* Ignore this error - pfifo_fast qeuue is full *\/ */ +/* } else */ + error(1, errno, "send_frame (line %d)", __LINE__); + } else { + count++; + msg_in_progress++; + } +} + static inline void get_next_timeout(struct timespec *timeout) { struct timespec now; static struct timespec last = {-1, 0 }; clock_gettime(CLOCK_MONOTONIC, &now); - + if (last.tv_sec == -1) last = now; - last.tv_sec += option_period_us/1000000; - last.tv_nsec += (option_period_us%1000000)*1000; - while (last.tv_nsec >= 1000000000) { - last.tv_nsec -= 1000000000; - last.tv_sec++; - } - timespec_subtract(timeout, &last, &now); + if (opt.period_us != 0) { + last.tv_sec += opt.period_us/1000000; + last.tv_nsec += (opt.period_us%1000000)*1000; + while (last.tv_nsec >= 1000000000) { + last.tv_nsec -= 1000000000; + last.tv_sec++; + } + if (timespec_subtract(timeout, &last, &now) /* is negative */) { + stats.overrun++; + memset(timeout, 0, sizeof(*timeout)); + } + } else if (opt.timeout_ms != 0) { + timeout->tv_sec = opt.timeout_ms/1000; + timeout->tv_nsec = (opt.timeout_ms%1000)*1000000; + } else + error(1, 0, "Timeout and period cannot be both zero"); } void receive(int s, struct can_frame *frame, struct timespec *ts_kern, struct timespec *ts_user) @@ -221,7 +540,7 @@ void receive(int s, struct can_frame *frame, struct timespec *ts_kern, struct ti /* these settings may be modified by recvmsg() */ iov.iov_len = sizeof(*frame); msg.msg_namelen = sizeof(addr); - msg.msg_controllen = sizeof(ctrlmsg); + msg.msg_controllen = sizeof(ctrlmsg); msg.msg_flags = 0; nbytes = recvmsg(s, &msg, 0); @@ -251,7 +570,16 @@ void process_tx(int s) void process_on_wire_rx(int s) { - error(1, 0, "%s: not implemented", __FUNCTION__); + struct timespec ts_kern, ts_user, ts_diff; + struct can_frame frame; + struct msg_info *mi; + receive(s, &frame, &ts_kern, &ts_user); + mi = frame2info(&frame); + if (msg_info_used(mi)) { + mi->ts_rx_onwire_kern = ts_kern; + mi->ts_rx_onwire = ts_user; + } else + stats.invalid_frame++; } @@ -260,30 +588,38 @@ void process_final_rx(int s) struct timespec ts_kern, ts_user, ts_diff; struct can_frame frame; struct msg_info *mi; + int ret; + receive(s, &frame, &ts_kern, &ts_user); mi = frame2info(&frame); mi->ts_rx_final_kern = ts_kern; mi->ts_rx_final = ts_user; + mi->received = frame; + + histogram_add(&histogram, get_msg_latency_us(mi)); - print_msg_info(mi); + ret = write(completion_pipe[1], &mi, sizeof(mi)); + if (ret == -1) + error(1, errno, "completion_pipe write"); } void *measure_thread(void *arg) { int s, i, ret; - struct pollfd pfd[MAX_INTERFACES]; + struct pollfd pfd[3]; struct timespec timeout; struct sockaddr_can addr; sigset_t set; + int consecutive_timeouts = 0; MEMSET_ZERO(pfd); - + for (i=0; i -d ... [other options]\n" - "Other options:\n" - " -d Interface to use. Must be given two times (tx, rx) or three times (tx, rx1, rx2).\n" - ); -} - -int parse_options(int argc, char *argv[]) +struct poptOption optionsTable[] = { + { "device", 'd', POPT_ARG_ARGV, &opt.interface, 'd', "Interface to use. Must be given two times (tx, rx) or three times (tx, rx1, rx2)", "interface" }, + { "count", 'c', POPT_ARG_INT|POPT_ARGFLAG_SHOW_DEFAULT, &opt.count, 0, "The count of messages to send, zero corresponds to infinity", "num"}, + { "id", 'i', POPT_ARG_INT|POPT_ARGFLAG_SHOW_DEFAULT, &opt.id, 0, "CAN ID of sent messages", "id"}, + { "period", 'p', POPT_ARG_INT|POPT_ARGFLAG_SHOW_DEFAULT, &opt.period_us, 0, "Period for sending messages or zero (default) to send as fast as possible", "us"}, + { "timeout",'t', POPT_ARG_INT|POPT_ARGFLAG_SHOW_DEFAULT, &opt.timeout_ms,0, "Timeout when period is zero", "ms"}, + { "oneattime",'o', POPT_ARG_NONE, &opt.oneattime,0, "Send the next message only when the previous was finally received"}, + { "verbose",'v', POPT_ARG_NONE, NULL, 'v', "Send the next message only when the previous was finally received"}, + { "name", 'n', POPT_ARG_STRING, &opt.name, 0, "Prefix of the generated files"}, + { "length", 'l', POPT_ARG_INT|POPT_ARGFLAG_SHOW_DEFAULT, &opt.length, 0, "The length of generated messages", "bytes"}, + { "userhist", 'u', POPT_ARG_NONE, &opt.userhist, 0, "Generate histogram from userspace timestamps"}, + { "quiet", 'q', POPT_ARG_NONE, &opt.quiet, 0, "Do not print progress and statistics"}, + POPT_AUTOHELP + { NULL, 0, 0, NULL, 0 } +}; + +int parse_options(int argc, const char *argv[]) { int c; - - opterr = 0; - while ((c = getopt (argc, argv, "d:h")) != -1) - switch (c) - { + poptContext optCon; /* context for parsing command-line options */ + void *local = talloc_new (NULL); + + optCon = poptGetContext(NULL, argc, argv, optionsTable, 0); + //poptSetOtherOptionHelp(optCon, "[OPTIONS]* "); + + /* Now do options processing */ + while ((c = poptGetNextOpt(optCon)) >= 0) { + switch (c) { case 'd': - if (num_interfaces < MAX_INTERFACES) { - option_interface[num_interfaces] = optarg; - num_interfaces++; - } else - error(1, 0, "error: Too many -d options"); + num_interfaces++; break; - case 'h': - print_help(); - exit(0); - break; - case '?': - if (isprint (optopt)) - error (1, 0, "Unknown option `-%c'.\n", optopt); - else - error (1, 0, "Unknown option character `\\x%x'.\n", optopt); - default: - error(1, 0, "Unhandled parameter"); } - if (num_interfaces < 2 || num_interfaces > 3) - error(1, 0, "-d option must be given exactly 2 or 3 times"); + } + if (c < -1) + error(1, 0, "%s: %s\n", + poptBadOption(optCon, POPT_BADOPTION_NOALIAS), + poptStrerror(c)); + + if (num_interfaces < 1 || num_interfaces > 3) + error(1, 0, "-d option must only be given one, two or three times"); + + if (opt.oneattime && opt.period_us) + error(1, 0, "oneattime and period cannot be specified at the same time"); + + if (opt.name) { + char *f = talloc_asprintf(local, "%s-msgs.txt", opt.name); + opt.f_msgs = fopen(f, "w"); + if (!opt.f_msgs) + error(1, errno, "fopen: %s", f); + } + + if (opt.name) { + char *f = talloc_asprintf(local, "%s-hist.txt", opt.name); + opt.f_hist = fopen(f, "w"); + if (!opt.f_hist) + error(1, errno, "fopen: %s", f); + } + + if (opt.name) { + char *f = talloc_asprintf(local, "%s-stat.txt", opt.name); + opt.f_stat = fopen(f, "w"); + if (!opt.f_stat) + error(1, errno, "fopen: %s", f); + } + + poptFreeContext(optCon); + talloc_free(local); return 0; } -int main(int argc, char *argv[]) +void print_progress() +{ + if (! opt.quiet) { + if (num_interfaces > 1) + printf("\rSent %5d, in progress %5d", count, msg_in_progress); + else + printf("\rSent %5d", count); + fflush(stdout); + } +} + +int main(int argc, const char *argv[]) { pthread_t thread; sigset_t set; - int ret; - + int ret, i; + parse_options(argc, argv); mlockall(MCL_CURRENT | MCL_FUTURE); @@ -401,14 +821,102 @@ int main(int argc, char *argv[]) signal(SIGINT, term_handler); signal(SIGTERM, term_handler); + for (i=0; i 0 && (pfd[0].revents & POLLIN)) { + struct msg_info *mi; + int ret; + ret = read(completion_pipe[0], &mi, sizeof(mi)); + if (ret < sizeof(mi)) + error(1, errno, "read completion returned %d", ret); + msg_info_print(opt.f_msgs, mi); + msg_info_free(mi); + completed++; + } + + clock_gettime(CLOCK_MONOTONIC, &now); + if (timespec_subtract(&diff, &next, &now)) { + print_progress(); + next.tv_nsec += 100000000; + while (next.tv_nsec >= 1000000000) { + next.tv_nsec -= 1000000000; + next.tv_sec++; + } + } + if (opt.count != 0 && count >= opt.count) { + if (allsent.tv_sec == 0) + allsent = now; + timespec_subtract(&diff, &now, &allsent); + if (diff.tv_sec >= 1) + finish_flag = 1; + } } + print_progress(); + if (!opt.quiet) + printf("\n"); + + stats.lost = msg_in_progress; pthread_join(thread, NULL); + close(completion_pipe[0]); + close(completion_pipe[1]); + + histogram_fprint(&histogram, opt.f_hist); + fclose(opt.f_hist); + fclose(opt.f_msgs); + + + fprintf(opt.f_stat, "cmdline='"); + for (i=0; i