Add first version of latency tester
authorMichal Sojka <sojkam1@fel.cvut.cz>
Sun, 28 Nov 2010 10:13:15 +0000 (11:13 +0100)
committerMichal Sojka <sojkam1@fel.cvut.cz>
Sun, 28 Nov 2010 10:13:15 +0000 (11:13 +0100)
latester/Makefile [new file with mode: 0644]
latester/Makefile.omk [new file with mode: 0644]
latester/histogram.h [new file with mode: 0644]
latester/latester.c [new file with mode: 0644]

diff --git a/latester/Makefile b/latester/Makefile
new file mode 100644 (file)
index 0000000..76b56fd
--- /dev/null
@@ -0,0 +1,14 @@
+# Generic directory or leaf node makefile for OCERA make framework
+
+ifndef MAKERULES_DIR
+MAKERULES_DIR := $(shell ( old_pwd="" ;  while [ ! -e Makefile.rules ] ; do if [ "$$old_pwd" = `pwd`  ] ; then exit 1 ; else old_pwd=`pwd` ; cd -L .. 2>/dev/null ; fi ; done ; pwd ) )
+endif
+
+ifeq ($(MAKERULES_DIR),)
+all : default
+.DEFAULT::
+       @echo -e "\nThe Makefile.rules has not been found in this or parent directory\n"
+else
+include $(MAKERULES_DIR)/Makefile.rules
+endif
+
diff --git a/latester/Makefile.omk b/latester/Makefile.omk
new file mode 100644 (file)
index 0000000..2a6c4b1
--- /dev/null
@@ -0,0 +1,5 @@
+# -*- makefile -*-
+
+bin_PROGRAMS += latester
+latester_SOURCES = latester.c
+latester_LIBS = rt pthread m ulut
diff --git a/latester/histogram.h b/latester/histogram.h
new file mode 100644 (file)
index 0000000..ad7591c
--- /dev/null
@@ -0,0 +1,54 @@
+#ifndef HISTOGRAM_H
+#define HISTOGRAM_H
+
+struct histogram {
+       unsigned *data;
+       unsigned allocated;
+       unsigned resolution;
+};
+
+int histogram_init(struct histogram *h,
+                  unsigned max_value,
+                  unsigned resolution)
+{
+       size_t mem;
+       h->allocated = max_value/resolution + 1;
+       h->resolution = resolution;
+       mem = h->allocated*sizeof(*h->data);
+       h->data = malloc(mem);
+       if (h->data) {
+               memset(h->data, 0, mem);
+               return 0;
+       } else
+               return -1;
+}
+
+void histogram_add(struct histogram *h, unsigned value)
+{
+       unsigned index = value / h->resolution;
+       if (index >= h->allocated)
+               index = h->allocated - 1;
+       h->data[index]++;
+}
+
+void histogram_fprint(struct histogram *h, FILE *f)
+{
+       unsigned long long sum = 0, cum;
+       unsigned i;
+       for (i = 0; i < h->allocated; i++)
+               sum += h->data[i];
+       cum = sum;
+       for (i = 0; i < h->allocated; i++) {
+               if (h->data[i] != 0) {
+                       if (!getenv("CANPING_MS"))
+                               fprintf(f, "%d %lld\n", i*h->resolution, cum);
+                       else
+                               fprintf(f, "%g %lld\n", 1e-3*(i*h->resolution), cum);
+               }
+               cum -= h->data[i];
+       }
+}
+
+
+
+#endif
diff --git a/latester/latester.c b/latester/latester.c
new file mode 100644 (file)
index 0000000..0329f48
--- /dev/null
@@ -0,0 +1,414 @@
+/**************************************************************************/
+/* CAN latency tester                                                     */
+/* Copyright (C) 2010 Michal Sojka, DCE FEE CTU Prague                    */
+/* License: GPLv2                                                        */
+/**************************************************************************/
+
+#include <ctype.h>
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <math.h>
+#include <net/if.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <ul_list.h>
+#include <unistd.h>
+#include <sched.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <linux/can.h>
+#include <linux/can/raw.h>
+#include <poll.h>
+
+#include "histogram.h"
+
+#ifndef DEBUG
+#define dbg(level, fmt, arg...) do {} while (0)
+#else
+#define dbg(level, fmt, arg...) do { if (level <= DEBUG) { printf("candping: " fmt, ## arg); } } while (0)
+#endif
+
+#define INTERRUPTED_SYSCALL(errno) (errno == EINTR || errno == ERESTART)
+
+#define MEMSET_ZERO(obj) memset(&(obj), 0, sizeof(obj))
+
+/* Global variables */
+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];
+int num_interfaces = 0;
+canid_t option_id;
+unsigned option_period_us = 100000;
+
+struct msg_info {
+       canid_t id;
+       uint8_t length;
+       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;
+};
+
+#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)
+{
+       uint16_t idx;
+       if (frame->can_dlc == 2) {
+               memcpy(&idx, frame->data, sizeof(idx));
+               if (idx >= MAX_INFOS)
+                       error(1, 0, "%s idx too high", __FUNCTION__);
+       } else
+               error(1, 0, "%s error", __FUNCTION__);
+       return &msg_infos[idx];
+}
+
+static inline char *tstamp_str(char *buf, struct timespec *tstamp)
+{
+       sprintf(buf, "%ld.%06ld", tstamp->tv_sec, tstamp->tv_nsec/1000);
+}
+
+void print_msg_info(struct msg_info *mi)
+{
+       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);
+}
+
+
+/* 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;
+       /* Perform the carry for the later subtraction by updating Y. */
+       if (x->tv_nsec < y->tv_nsec) {
+               int nsec = (y->tv_nsec - x->tv_nsec) / 1000000000 + 1;
+               y->tv_nsec -= 1000000000 * nsec;
+               y->tv_sec += nsec;
+       }
+       if (x->tv_nsec - y->tv_nsec > 1000000000) {
+               int nsec = (x->tv_nsec - y->tv_nsec) / 1000000000;
+               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;
+}
+
+void dbg_print_timespec(char *msg, struct timespec *tv)
+{
+
+       printf("%s sec=%ld nsec=%ld\n", msg, tv->tv_sec, tv->tv_nsec);
+}
+
+void set_sched_policy_and_prio(int policy, int rtprio)
+{
+       struct sched_param scheduling_parameters;
+       int maxprio=sched_get_priority_max(policy);
+       int minprio=sched_get_priority_min(policy);
+
+       if((rtprio < minprio) || (rtprio > maxprio))
+               error(1, 0, "The priority for requested policy is out of <%d, %d> range\n",
+                     minprio, maxprio);
+
+       scheduling_parameters.sched_priority = rtprio;
+
+       if (0 != pthread_setschedparam(pthread_self(), policy, &scheduling_parameters))
+               error(1, errno, "pthread_setschedparam error");
+}
+
+void term_handler(int signum)
+{
+       finish_flag = 1;
+}
+
+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");
+       return ifr.ifr_ifindex;
+}
+
+static inline get_tstamp(struct timespec *ts)
+{
+       clock_gettime(CLOCK_REALTIME, ts);
+}
+
+int send_frame(int socket)
+{
+       struct can_frame frame;
+       struct msg_info *mi;
+       int ret;
+
+       frame.can_id = option_id;
+       frame.can_dlc = 2;
+       memcpy(frame.data, &curr_msg, sizeof(curr_msg));
+       mi = frame2info(&frame);
+
+       get_tstamp(&mi->ts_sent);
+       ret = write(socket, &frame, sizeof(frame));
+       return ret;
+}
+
+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);
+}
+
+void receive(int s, struct can_frame *frame, struct timespec *ts_kern, struct timespec *ts_user)
+{
+       char ctrlmsg[CMSG_SPACE(sizeof(struct timeval)) + CMSG_SPACE(sizeof(__u32))];
+       struct iovec iov;
+       struct msghdr msg;
+       struct cmsghdr *cmsg;
+       struct sockaddr_can addr;
+       int nbytes;
+       static uint64_t dropcnt = 0;
+
+       iov.iov_base = frame;
+       msg.msg_name = &addr;
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+       msg.msg_control = &ctrlmsg;
+
+       /* these settings may be modified by recvmsg() */
+       iov.iov_len = sizeof(*frame);
+       msg.msg_namelen = sizeof(addr);
+       msg.msg_controllen = sizeof(ctrlmsg);  
+       msg.msg_flags = 0;
+
+       nbytes = recvmsg(s, &msg, 0);
+       if (nbytes < 0)
+               error(1, errno, "recvmsg");
+
+       if (nbytes < sizeof(struct can_frame))
+               error(1, 0, "recvmsg: incomplete CAN frame\n");
+
+       get_tstamp(ts_user);
+       MEMSET_ZERO(*ts_kern);
+       for (cmsg = CMSG_FIRSTHDR(&msg);
+            cmsg && (cmsg->cmsg_level == SOL_SOCKET);
+            cmsg = CMSG_NXTHDR(&msg,cmsg)) {
+               if (cmsg->cmsg_type == SO_TIMESTAMPNS)
+                       *ts_kern = *(struct timespec *)CMSG_DATA(cmsg);
+               else if (cmsg->cmsg_type == SO_RXQ_OVFL)
+                       dropcnt += *(__u32 *)CMSG_DATA(cmsg);
+       }
+
+}
+
+void process_tx(int s)
+{
+       error(1, 0, "%s: not implemented", __FUNCTION__);
+}
+
+void process_on_wire_rx(int s)
+{
+       error(1, 0, "%s: not implemented", __FUNCTION__);
+}
+
+
+void process_final_rx(int s)
+{
+       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);
+       mi->ts_rx_final_kern = ts_kern;
+       mi->ts_rx_final = ts_user;
+
+       print_msg_info(mi);
+}
+
+void *measure_thread(void *arg)
+{
+       int s, i, ret;
+       struct pollfd pfd[MAX_INTERFACES];
+       struct timespec timeout;
+       struct sockaddr_can addr;
+       sigset_t set;
+
+       MEMSET_ZERO(pfd);
+       
+       for (i=0; i<num_interfaces; i++) {
+               if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0)
+                       error(1, errno, "socket");
+
+               addr.can_family = AF_CAN;
+               addr.can_ifindex = sock_get_if_index(s, option_interface[i]);
+
+               if (i == 0) {   /* TX socket */
+                       /* disable default receive filter on this RAW socket */
+                       /* This is obsolete as we do not read from the socket at all, but for */
+                       /* this reason we can remove the receive list in the Kernel to save a */
+                       /* little (really a very little!) CPU usage.                          */
+                       if (setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0) == -1)
+                               error(1, errno, "SOL_CAN_RAW");
+               }
+
+               if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+                       error(1, errno, "bind");
+
+               const int timestamp_on = 1;
+               if (setsockopt(s, SOL_SOCKET, SO_TIMESTAMPNS,
+                              &timestamp_on, sizeof(timestamp_on)) < 0)
+                       error(1, errno, "setsockopt SO_TIMESTAMP");
+
+               const int dropmonitor_on = 1;
+               if (setsockopt(s, SOL_SOCKET, SO_RXQ_OVFL,
+                              &dropmonitor_on, sizeof(dropmonitor_on)) < 0)
+                       error(1, errno, "setsockopt SO_RXQ_OVFL not supported by your Linux Kernel");
+
+               pfd[i].fd = s;
+               pfd[i].events = (i == 0) ? POLLERR : POLLIN;
+       }
+
+       set_sched_policy_and_prio(SCHED_FIFO, 99);
+
+       while (!finish_flag) {
+               get_next_timeout(&timeout);
+               ret = ppoll(pfd, num_interfaces, &timeout, NULL);
+               switch (ret) {
+               case -1:
+                       if (!INTERRUPTED_SYSCALL(errno))
+                               error(1, errno, "ppoll");
+                       break;
+               case 0:
+                       ret = send_frame(pfd[0].fd);
+                       if (ret != sizeof(struct can_frame))
+                               error(1, errno, "send_frame");
+                       break;
+               default:
+                       if (pfd[0].revents != 0) {
+                               process_tx(pfd[0].fd);
+                       }
+                       if (pfd[1].revents != 0) {
+                               switch (num_interfaces) {
+                               case 2: process_final_rx(pfd[1].fd); break;
+                               case 3: process_on_wire_rx(pfd[1].fd); break;
+                               }
+                       }
+                       if (pfd[2].revents != 0) {
+                               process_final_rx(pfd[2].fd);
+                       }
+               }
+       }
+
+       for (i=0; i<num_interfaces; i++)
+               close(pfd[i].fd);
+
+       return NULL;
+}
+
+void print_help(void)
+{
+       printf("Usage: latester -d <interface> -d <interface> ... [other options]\n"
+              "Other options:\n"
+              "  -d <interface> Interface to use. Must be given two times (tx, rx) or three times (tx, rx1, rx2).\n"
+               );
+}
+     
+int parse_options(int argc, char *argv[])
+{
+       int c;
+       
+       opterr = 0;
+       while ((c = getopt (argc, argv, "d:h")) != -1)
+               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");
+                       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");
+       return 0;
+}
+
+int main(int argc, char *argv[])
+{
+       pthread_t thread;
+       sigset_t set;
+       int ret;
+                          
+       parse_options(argc, argv);
+
+       mlockall(MCL_CURRENT | MCL_FUTURE);
+
+       signal(SIGINT, term_handler);
+       signal(SIGTERM, term_handler);
+
+
+       pthread_create(&thread, 0, measure_thread, NULL);
+
+       while (!finish_flag) {
+               sleep(1);
+       }
+
+       pthread_join(thread, NULL);
+
+       return 0;
+}