diff --git a/canbusload.c b/canbusload.c index d2906ae..753d658 100644 --- a/canbusload.c +++ b/canbusload.c @@ -65,27 +65,32 @@ #include "terminal.h" #include "canframelen.h" -#define MAXSOCK 16 /* max. number of CAN interfaces given on the cmdline */ +#define ANYDEV "any" /* name of interface to receive from any CAN interface */ +#define MAXDEVS 20 /* max. number of CAN interfaces given on the cmdline */ #define PERCENTRES 5 /* resolution in percent for bargraph */ #define NUMBAR (100 / PERCENTRES) /* number of bargraph elements */ +#define BRSTRLEN 20 extern int optind, opterr, optopt; static struct { char devname[IFNAMSIZ + 1]; + char bitratestr[BRSTRLEN]; /* 100000/2000000 => 100k/2M */ + char recv_direction; + int ifindex; unsigned int bitrate; unsigned int dbitrate; unsigned int recv_frames; unsigned int recv_bits_total; unsigned int recv_bits_payload; unsigned int recv_bits_dbitrate; -} stat[MAXSOCK + 1]; +} stat[MAXDEVS + 1]; static volatile int running = 1; static volatile sig_atomic_t signal_num; static int max_devname_len; /* to prevent frazzled device name output */ -static int max_bitrate_len; +static int max_bitratestr_len; static int currmax; static unsigned char redraw; static unsigned char timestamp; @@ -107,20 +112,20 @@ static void print_usage(char *prg) fprintf(stderr, " -i (ignore bitstuffing in bandwidth calculation)\n"); fprintf(stderr, " -e (exact calculation of stuffed bits)\n"); fprintf(stderr, "\n"); - fprintf(stderr, "Up to %d CAN interfaces with mandatory bitrate can be specified on the \n", MAXSOCK); - fprintf(stderr, "commandline in the form: @[,]\n\n"); + fprintf(stderr, "Up to %d CAN interfaces with mandatory bitrate can be specified on the \n", MAXDEVS); + fprintf(stderr, "commandline in the form: @[,]\n"); + fprintf(stderr, "The interface name 'any' enables an auto detection with the given bitrate[s]\n\n"); fprintf(stderr, "The bitrate is mandatory as it is needed to know the CAN bus bitrate to\n"); fprintf(stderr, "calculate the bus load percentage based on the received CAN frames.\n"); fprintf(stderr, "Due to the bitstuffing estimation the calculated busload may exceed 100%%.\n"); fprintf(stderr, "For each given interface the data is presented in one line which contains:\n\n"); - fprintf(stderr, "(interface) (received CAN frames) (used bits total) (used bits for payload)\n"); + fprintf(stderr, "(interface) (received CAN frames) (bits total) (bits payload) (bits payload brs)\n"); fprintf(stderr, "\nExamples:\n"); - fprintf(stderr, "\nuser$> canbusload can0@100000 can1@500000 can2@500000 can3@500000 -r -t -b -c\n\n"); - fprintf(stderr, "%s 2014-02-01 21:13:16 (worst case bitstuffing)\n", prg); - fprintf(stderr, " can0@100000 805 74491 36656 74%% |XXXXXXXXXXXXXX......|\n"); - fprintf(stderr, " can1@500000 796 75140 37728 15%% |XXX.................|\n"); - fprintf(stderr, " can2@500000 0 0 0 0%% |....................|\n"); - fprintf(stderr, " can3@500000 47 4633 2424 0%% |....................|\n"); + fprintf(stderr, "\nuser$> canbusload can0@100000 can1@500000,2000000 can2@500000 -r -t -b -c\n\n"); + fprintf(stderr, "%s 2024-08-08 16:30:05 (worst case bitstuffing)\n", prg); + fprintf(stderr, " can0@100k 192 21980 9136 0 21%% |TTTT................|\n"); + fprintf(stderr, " can1@500k/2M 2651 475500 234448 131825 74%% |XXXXXXXXXXXXXX......|\n"); + fprintf(stderr, " can2@500k 855 136777 62968 35219 27%% |RRRRR...............|\n"); fprintf(stderr, "\n"); } @@ -130,6 +135,32 @@ static void sigterm(int signo) signal_num = signo; } +static int add_bitrate(char *brstr, unsigned int bitrate) +{ + if (bitrate % 1000000 == 0) + return sprintf(brstr, "%dM", bitrate / 1000000); + + if (bitrate % 1000 == 0) + return sprintf(brstr, "%dk", bitrate / 1000); + + return sprintf(brstr, "%d", bitrate); +} + +static void create_bitrate_string(int stat_idx, int *max_bitratestr_len) +{ + int ptr; + + ptr = add_bitrate(&stat[stat_idx].bitratestr[0], stat[stat_idx].bitrate); + + if (stat[stat_idx].bitrate != stat[stat_idx].dbitrate) { + ptr += sprintf(&stat[stat_idx].bitratestr[ptr], "/"); + ptr += add_bitrate(&stat[stat_idx].bitratestr[ptr], stat[stat_idx].dbitrate); + } + + if (ptr > *max_bitratestr_len) + *max_bitratestr_len = ptr; +} + static void printstats(int signo) { int i, j, percent; @@ -194,9 +225,9 @@ static void printstats(int signo) else percent = 0; - printf(" %*s@%-*d %5d %7d %6d %6d %3d%%", + printf(" %*s@%-*s %5d %7d %7d %7d %3d%%", max_devname_len, stat[i].devname, - max_bitrate_len, stat[i].bitrate, + max_bitratestr_len, stat[i].bitratestr, stat[i].recv_frames, stat[i].recv_bits_total, stat[i].recv_bits_payload, @@ -212,7 +243,7 @@ static void printstats(int signo) for (j = 0; j < NUMBAR; j++) { if (j < percent / PERCENTRES) - printf("X"); + printf("%c", stat[i].recv_direction); else printf("."); } @@ -230,6 +261,7 @@ static void printstats(int signo) stat[i].recv_bits_total = 0; stat[i].recv_bits_dbitrate = 0; stat[i].recv_bits_payload = 0; + stat[i].recv_direction = '.'; } if (!redraw) @@ -243,14 +275,19 @@ static void printstats(int signo) int main(int argc, char **argv) { fd_set rdfs; - int s[MAXSOCK]; - + int s; int opt; char *ptr, *nptr; struct sockaddr_can addr; struct canfd_frame frame; + struct iovec iov; + struct msghdr msg; int nbytes, i; - struct ifreq ifr; + + int have_anydev = 0; + unsigned int anydev_bitrate = 0; + unsigned int anydev_dbitrate = 0; + char anydev_bitratestr[BRSTRLEN]; /* 100000/2000000 => 100k/2M */ signal(SIGTERM, sigterm); signal(SIGHUP, sigterm); @@ -300,27 +337,22 @@ int main(int argc, char **argv) currmax = argc - optind; /* find real number of CAN devices */ - if (currmax > MAXSOCK) { - printf("More than %d CAN devices given on commandline!\n", MAXSOCK); + if (currmax > MAXDEVS) { + printf("More than %d CAN devices given on commandline!\n", MAXDEVS); return 1; } + /* prefill stat[] array with given interface assignments */ for (i = 0; i < currmax; i++) { - ptr = argv[optind + i]; + ptr = argv[optind + i + have_anydev]; nbytes = strlen(ptr); - if (nbytes >= (int)(IFNAMSIZ + sizeof("@1000000") + 1)) { + if (nbytes >= (int)(IFNAMSIZ + sizeof("@1000000,2000000") + 1)) { printf("name of CAN device '%s' is too long!\n", ptr); return 1; } - pr_debug("open %d '%s'.\n", i, ptr); - - s[i] = socket(PF_CAN, SOCK_RAW, CAN_RAW); - if (s[i] < 0) { - perror("socket"); - return 1; - } + pr_debug("handle %d '%s'.\n", i, ptr); nptr = strchr(ptr, '@'); @@ -330,55 +362,76 @@ int main(int argc, char **argv) return 1; } + /* interface name length */ nbytes = nptr - ptr; /* interface name is up the first '@' */ - if (nbytes >= (int)IFNAMSIZ) { printf("name of CAN device '%s' is too long!\n", ptr); return 1; } + /* copy interface name to stat[] entry */ strncpy(stat[i].devname, ptr, nbytes); - memset(&ifr.ifr_name, 0, sizeof(ifr.ifr_name)); - strncpy(ifr.ifr_name, ptr, nbytes); if (nbytes > max_devname_len) max_devname_len = nbytes; /* for nice printing */ char *endp; - stat[i].bitrate = strtol(nptr + 1, &endp, 0); /* bitrate is placed behind the '@' */ + /* bitrate is placed behind the '@' */ + stat[i].bitrate = strtol(nptr + 1, &endp, 0); + + /* check for CAN FD additional data bitrate */ if (*endp == ',') /* data bitrate is placed behind the ',' */ stat[i].dbitrate = strtol(endp + 1, &endp, 0); else stat[i].dbitrate = stat[i].bitrate; - if (!stat[i].bitrate || stat[i].bitrate > 1000000) { + if (!stat[i].bitrate || stat[i].bitrate > 1000000 || + !stat[i].dbitrate || stat[i].dbitrate > 8000000) { printf("invalid bitrate for CAN device '%s'!\n", ptr); return 1; } - nbytes = strlen(nptr + 1); - if (nbytes > max_bitrate_len) - max_bitrate_len = nbytes; /* for nice printing */ + /* prepare bitrate string for hot path */ + create_bitrate_string(i, &max_bitratestr_len); - pr_debug("using interface name '%s'.\n", ifr.ifr_name); + stat[i].recv_direction = '.'; - /* try to switch the socket into CAN FD mode */ - const int canfd_on = 1; - setsockopt(s[i], SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfd_on, sizeof(canfd_on)); - - if (ioctl(s[i], SIOCGIFINDEX, &ifr) < 0) { - perror("SIOCGIFINDEX"); - exit(1); + /* handling for 'any' device */ + if (have_anydev == 0 && strcmp(ANYDEV, stat[i].devname) == 0) { + anydev_bitrate = stat[i].bitrate; + anydev_dbitrate = stat[i].dbitrate; + memcpy(anydev_bitratestr, stat[i].bitratestr, BRSTRLEN); + /* no real interface: remove this command line entry */ + have_anydev = 1; + currmax--; + i--; + } else { + stat[i].ifindex = if_nametoindex(stat[i].devname); + if (!stat[i].ifindex) { + printf("invalid CAN device '%s'!\n", stat[i].devname); + return 1; + } + pr_debug("using interface name '%s'.\n", stat[i].devname); } + } - addr.can_family = AF_CAN; - addr.can_ifindex = ifr.ifr_ifindex; + s = socket(PF_CAN, SOCK_RAW, CAN_RAW); + if (s < 0) { + perror("socket"); + return 1; + } - if (bind(s[i], (struct sockaddr *)&addr, sizeof(addr)) < 0) { - perror("bind"); - return 1; - } + /* try to switch the socket into CAN FD mode */ + const int canfd_on = 1; + setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canfd_on, sizeof(canfd_on)); + + addr.can_family = AF_CAN; + addr.can_ifindex = 0; /* any CAN device */ + + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + perror("bind"); + return 1; } alarm(1); @@ -386,43 +439,93 @@ int main(int argc, char **argv) if (redraw) printf("%s", CLR_SCREEN); + /* these settings are static and can be held out of the hot path */ + iov.iov_base = &frame; + msg.msg_name = &addr; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + while (running) { FD_ZERO(&rdfs); - for (i = 0; i < currmax; i++) - FD_SET(s[i], &rdfs); + FD_SET(s, &rdfs); - if (select(s[currmax - 1] + 1, &rdfs, NULL, NULL, NULL) < 0) { + if (select(s + 1, &rdfs, NULL, NULL, NULL) < 0) { //perror("pselect"); continue; } - for (i = 0; i < currmax; i++) { /* check all CAN RAW sockets */ + /* these settings may be modified by recvmsg() */ + iov.iov_len = sizeof(frame); + msg.msg_namelen = sizeof(addr); + msg.msg_controllen = 0; + msg.msg_flags = 0; - if (FD_ISSET(s[i], &rdfs)) { - nbytes = read(s[i], &frame, sizeof(frame)); + nbytes = recvmsg(s, &msg, 0); - if (nbytes < 0) { - perror("read"); - return 1; - } - - if (nbytes < (int)sizeof(struct can_frame)) { - fprintf(stderr, "read: incomplete CAN frame\n"); - return 1; - } - - stat[i].recv_frames++; - stat[i].recv_bits_payload += frame.len * 8; - stat[i].recv_bits_dbitrate += can_frame_dbitrate_length( - &frame, mode, sizeof(frame)); - stat[i].recv_bits_total += can_frame_length(&frame, - mode, nbytes); - } + if (nbytes < 0) { + perror("read"); + return 1; } + + if (nbytes != (int)sizeof(struct can_frame) && + nbytes != (int)sizeof(struct canfd_frame)) { + fprintf(stderr, "read: incomplete CAN frame\n"); + return 1; + } + + /* find received ifindex in stat[] array */ + for (i = 0; i < currmax; i++) { + if (stat[i].ifindex == addr.can_ifindex) + break; + } + + /* not found? check for unknown interface */ + if (i >= currmax) { + /* drop unwanted traffic */ + if (have_anydev == 0) + continue; + + /* can we add another interface? */ + if (currmax >= MAXDEVS) + continue; + + /* add an new entry */ + stat[i].ifindex = addr.can_ifindex; + stat[i].bitrate = anydev_bitrate; + stat[i].dbitrate = anydev_dbitrate; + memcpy(stat[i].bitratestr, anydev_bitratestr, BRSTRLEN); + stat[i].recv_direction = '.'; + if_indextoname(addr.can_ifindex, stat[i].devname); + nbytes = strlen(stat[i].devname); + if (nbytes > max_devname_len) + max_devname_len = nbytes; /* for nice printing */ + currmax++; + } + + if (msg.msg_flags & MSG_DONTROUTE) { + /* TX direction */ + if (stat[i].recv_direction == '.') + stat[i].recv_direction = 'T'; + else if (stat[i].recv_direction == 'R') + stat[i].recv_direction = 'X'; + } else { + /* RX direction */ + if (stat[i].recv_direction == '.') + stat[i].recv_direction = 'R'; + else if (stat[i].recv_direction == 'T') + stat[i].recv_direction = 'X'; + } + + stat[i].recv_frames++; + stat[i].recv_bits_payload += frame.len * 8; + stat[i].recv_bits_dbitrate += can_frame_dbitrate_length( + &frame, mode, sizeof(frame)); + stat[i].recv_bits_total += can_frame_length(&frame, + mode, nbytes); } - for (i = 0; i < currmax; i++) - close(s[i]); + close(s); if (signal_num) return 128 + signal_num;