/* File: vca_canping.c - utility to test CAN functionality and throughput */
/* */
/* LibVCA - Versatile CAN/CANopen API library */
-/* Copyright (C) 2005-2006 Michal Sojka, DCE FEE CTU Prague */
+/* Copyright (C) 2005-2009 Michal Sojka, DCE FEE CTU Prague */
/* Copyright (C) 2006-2009 Pavel Pisa <pisa@cmp.felk.cvut.cz> */
/* */
/* LibVCA is free software; you can redistribute it and/or modify it */
#include <ul_list.h>
#include <errno.h>
#include <semaphore.h>
+#include <stdbool.h>
+#include <error.h>
/* TODO: Handle the case where there are more canping slaves running
* on one CAN bus. */
#ifdef WITH_RTPRIO
#include <sched.h>
+#include <sys/mman.h>
int sched_policy = SCHED_OTHER;
int sched_rtprio;
int option_timeout = 4;
int option_open_once = 0;
int option_synch_start = 0;
-
+bool option_background = false;
+char *option_histogram = NULL;
+int option_msg_to_ignore = 0;
+char *option_save_all_times = NULL;
/* Lists */
typedef struct threads {
ul_list_head_t head;
} threads_t;
+struct histogram {
+ unsigned *data;
+ unsigned allocated;
+ unsigned resolution;
+};
+
typedef struct thread_data {
pthread_t tid;
long int canid;
int min, max; /* min/max response times */
int timeout; /* number of timeouts */
+ struct histogram hist;
+ unsigned *times; /* Array of all measured times */
+
ul_list_node_t node;
} thread_data_t;
threads_t master_threads;
threads_t slave_threads;
+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];
+ }
+}
+
/* Subtract the `struct timeval' values X and Y, storing the result in
RESULT. Return 1 if the difference is negative, otherwise 0. */
perror("ioctl CANQUE_QUEUE_FLUSH");
exit(EXIT_FLUSH_ERROR);
}
+
+ if (option_histogram)
+ histogram_init(&td->hist, 1000*1000/*max [us]*/, 5/*resolution [us]*/);
+
+ if (option_save_all_times) {
+ td->times = malloc(sizeof(*td->times)*option_count);
+ if (!td->times)
+ error(1, errno, "malloc times");
+ }
/* Prepare data structures for the select syscall. */
FD_ZERO (&rdset);
pthread_mutex_unlock(&mut_start);
}
- while (!IS_FINISH_FLAG() && (option_count == 0 || ping_count++ < option_count)) {
+ while (!IS_FINISH_FLAG() && (option_count == 0 || ping_count++ < option_count + option_msg_to_ignore)) {
/* Send a ping message */
pingmsg.flags=0;
pingmsg.id=ping_id;
printf("%d:%ld\n", ping_id, time);
break;
case 3:
- printf("Pong response for id %d received in %ld us\n", ping_id, time);
+ printf("Pong response %d for id %d received in %ld us\n", ping_count, ping_id, time);
break;
}
/* Update statistics */
- td->count++;
- td->mean =
- td->mean * ((double)(td->count - 1) / td->count) +
- (double)time / td->count;
- td->moment2nd =
- td->moment2nd * ((double)(td->count - 1) / td->count) +
- (double)time*time / td->count;
- if (time > td->max) td->max = time;
- if (time < td->min) td->min = time;
+ if (ping_count > option_msg_to_ignore) {
+ td->count++;
+ td->mean =
+ td->mean * ((double)(td->count - 1) / td->count) +
+ (double)time / td->count;
+ td->moment2nd =
+ td->moment2nd * ((double)(td->count - 1) / td->count) +
+ (double)time*time / td->count;
+ if (time > td->max) td->max = time;
+ if (time < td->min) td->min = time;
+ if (option_histogram)
+ histogram_add(&td->hist, time);
+ if (option_save_all_times)
+ td->times[ping_count - 1 - option_msg_to_ignore] = time;
+ }
total_count++;
} /* read */
}
vca_close_handle(vcah);
}
+ if (option_histogram) {
+ FILE *f;
+ char buf[100];
+ snprintf(buf, sizeof(buf), "%s-%d.dat", option_histogram, ping_id);
+ f = fopen(buf, "w");
+ histogram_fprint(&td->hist, f);
+ fclose(f);
+ }
+
+ if (option_save_all_times) {
+ FILE *f;
+ char buf[100];
+ int i;
+ snprintf(buf, sizeof(buf), "%s-%d.dat", option_save_all_times, ping_id);
+ f = fopen(buf, "w");
+ for (i=0; i<option_count; i++) {
+ if (!getenv("CANPING_MS"))
+ fprintf(f, "%u\n", td->times[i]);
+ else
+ fprintf(f, "%g\n", 1e-3*td->times[i]);
+ }
+ fclose(f);
+ }
+
sem_post(&finish_sem);
return (void *)ret;
}
pongmsg.length = option_length;
for (i=0; i < option_length; i++) pongmsg.data[i] = i;
+ /* Signal that I'm ready */
+ sem_post(&ready_sem);
+
while (!IS_FINISH_FLAG()) {
/* Receive a ping message */
pingmsg.flags=0;
pthread_create(&td->tid, NULL, slave_thread, (void *)td);
dbg(2, "Slave thread: %p\n", (void *)td->tid);
}
+
+ /* Wait for all threads beeing ready */
+ for (i = 0; i < slaves; i++) sem_wait(&ready_sem);
}
#ifdef WITH_RTPRIO
printf("Usage: canping -m <master threads> [other options]\n"
" canping -s <slave threads> [other options]\n\n"
"Other options:\n"
+ " -b go to background (fork) after initialization, prints child PID\n"
" -c count how many messages each master sends\n"
" -d dev device (e.g. /dev/can1)\n"
+ " -e prefix save all measured times in file <prefix>-<id>.dat\n"
+ " -g prefix store cumulative histogram in file <prefix>-<id>.dat\n"
" -h print this help\n"
" -i id id of first master message\n"
" -l length length of the messages (0..8)\n"
+ " -n number ignore first <number> messages\n"
" -o open a device only once for all threads (doesn't work)\n" /* due to filters */
" -t timeout timeout in seconds (default 4 s)\n"
" -v be verbose (use more than once to increase verbosity)\n"
int c;
opterr = 0;
- while ((c = getopt (argc, argv, "c:d:hi:l:m:os:t:vw:yrR:")) != -1)
+ while ((c = getopt (argc, argv, "bc:d:e:g:hi:l:m:n:os:t:vw:yrR:")) != -1)
switch (c)
{
+ case 'b':
+ option_background = true;
+ break;
case 'c':
option_count = atoi(optarg);
break;
case 'd':
option_device = optarg;
break;
+ case 'e':
+ option_save_all_times = optarg;
+ break;
+ case 'g':
+ option_histogram = optarg;
+ break;
case 'h':
print_help();
exit(EXIT_OK);
case 'm':
option_masters = atoi(optarg);
break;
+ case 'n':
+ option_msg_to_ignore = atoi(optarg);
+ break;
case 'o':
option_open_once = 1;
break;
parse_options(argc, argv);
#ifdef WITH_RTPRIO
- if(sched_policy != SCHED_OTHER)
+ if(sched_policy != SCHED_OTHER) {
if(set_sched_policy_and_prio(sched_policy, sched_rtprio) <0)
exit(EXIT_BAD_PARAM);
+ mlockall(MCL_CURRENT | MCL_FUTURE);
+ }
#endif
}
}
- if (option_masters) start_masters(option_masters, option_first_id);
- if (option_slaves) start_slaves(option_slaves, option_first_id);
+ sem_t *child_ready;
+ int fork_ret = 0;
+ if ((child_ready = sem_open("canping", O_CREAT)) == NULL)
+ error(1, errno, "sem_open");
+ if (option_background) {
+ /* Go to background when everything is ready */
+ fork_ret = fork();
+ if (fork_ret < 0)
+ error(1, errno, "Cannot go to background");
+ if (fork_ret == 0) {
+ int devnull = open("/dev/null", O_WRONLY);
+ dup2(devnull, 1);
+ }
+ }
+ if (fork_ret == 0) {
+ /* Child */
+ if (option_masters) start_masters(option_masters, option_first_id);
+ if (option_slaves) start_slaves(option_slaves, option_first_id);
+ sem_post(child_ready);
+ } else {
+ /* Parent */
+ sem_wait(child_ready);
+ printf("%d\n", fork_ret);
+ exit(0);
+ }
+
wait_for_threads();
if (option_open_once) {