Add a unittest program for clock synchronization modules
[lttv.git] / lttv / lttv / sync / unittest.c
diff --git a/lttv/lttv/sync/unittest.c b/lttv/lttv/sync/unittest.c
new file mode 100644 (file)
index 0000000..4ca2b93
--- /dev/null
@@ -0,0 +1,716 @@
+/* This file is part of the Linux Trace Toolkit viewer
+ * Copyright (C) 2009 Benjamin Poirier <benjamin.poirier@polymtl.ca>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2 as
+ * published by the Free Software Foundation;
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
+ * MA 02111-1307, USA.
+ */
+
+#define _GNU_SOURCE
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "sync_chain.h"
+
+
+#ifndef g_info
+#define g_info(format...) g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, format)
+#endif
+
+
+static void timeDiff(struct timeval* const end, const struct timeval* const start);
+static void usage(const char* const programName);
+static gint gcfCompareAnalysis(gconstpointer a, gconstpointer b);
+static void gfAppendAnalysisName(gpointer data, gpointer user_data);
+static unsigned int readTraceNb(FILE* testCase);
+static void skipCommentLines(FILE* testCase);
+static void processEvents(SyncState* const syncState, FILE* testCase);
+static void nullLog(const gchar *log_domain, GLogLevelFlags log_level, const
+       gchar *message, gpointer user_data);
+
+GQueue processingModules= G_QUEUE_INIT;
+GQueue matchingModules= G_QUEUE_INIT;
+GQueue analysisModules= G_QUEUE_INIT;
+
+// time values in test case files will be scaled by this factor
+const double freq= 1e9;
+
+
+/*
+ * Create matching and analysis modules and feed them events read from a text
+ * file.
+ *
+ * Idealy, this would've been a processing module but sync_chain.c and
+ * ProcessingModule use some LTTV-specific types and functions. Unfortunately,
+ * there is some code duplication from sync_chain.c
+ *
+ */
+int main(int argc, char* argv[])
+{
+       int c;
+       extern char* optarg;
+       extern int optind, opterr, optopt;
+       bool optionSyncStats= false;
+       char* optionGraphsDir= NULL;
+       FILE* testCase= NULL;
+
+       SyncState* syncState;
+       struct timeval startTime, endTime;
+       struct rusage startUsage, endUsage;
+       GList* result;
+       char* cwd;
+       FILE* graphsStream;
+       int graphsFp;
+       GArray* factors;
+
+       int retval;
+
+       syncState= malloc(sizeof(SyncState));
+
+       g_assert(g_queue_get_length(&analysisModules) > 0);
+       syncState->analysisModule= g_queue_peek_head(&analysisModules);
+
+       do
+       {
+               int optionIndex= 0;
+
+               static struct option longOptions[]=
+               {
+                       {"sync-stats", no_argument, 0, 's'},
+                       {"sync-graphs", optional_argument, 0, 'g'},
+                       {"sync-analysis", required_argument, 0, 'a'},
+                       {0, 0, 0, 0}
+               };
+
+               c= getopt_long(argc, argv, "sg::a:", longOptions, &optionIndex);
+
+               switch (c)
+               {
+                       case -1:
+                       case 0:
+                               break;
+
+                       case 's':
+                               if (!optionSyncStats)
+                               {
+                                       gettimeofday(&startTime, 0);
+                                       getrusage(RUSAGE_SELF, &startUsage);
+                               }
+                               optionSyncStats= true;
+                               break;
+
+                       case 'g':
+                               if (optarg)
+                               {
+                                       printf("xxx:%s\n", optarg);
+                                       optionGraphsDir= malloc(strlen(optarg));
+                                       strcpy(optionGraphsDir, optarg);
+                               }
+                               else
+                               {
+                                       optionGraphsDir= malloc(20);
+                                       retval= snprintf(optionGraphsDir, 20, "graphs-%d",
+                                               getpid());
+                                       if (retval > 20 - 1)
+                                       {
+                                               optionGraphsDir[20 - 1]= '\0';
+                                       }
+                               }
+                               break;
+
+                       case 'a':
+                               printf("xxx:%s\n", optarg);
+                               result= g_queue_find_custom(&analysisModules, optarg,
+                                       &gcfCompareAnalysis);
+                               if (result != NULL)
+                               {
+                                       syncState->analysisModule= (AnalysisModule*) result->data;
+                               }
+                               else
+                               {
+                                       g_error("Analysis module '%s' not found", optarg);
+                               }
+                               break;
+
+                       case '?':
+                               usage(argv[0]);
+                               abort();
+
+                       default:
+                               g_error("Option parse error");
+               }
+       } while (c != -1);
+
+       if (argc <= optind)
+       {
+               fprintf(stderr, "Test file unspecified\n");
+               usage(argv[0]);
+               abort();
+       }
+
+       testCase= fopen(argv[optind], "r");
+       if (testCase == NULL)
+       {
+               g_error(strerror(errno));
+       }
+
+       // Initialize data structures
+       syncState->traceNb= readTraceNb(testCase);
+
+       if (optionSyncStats)
+       {
+               syncState->stats= true;
+       }
+       else
+       {
+               syncState->stats= false;
+       }
+
+       syncState->graphs= optionGraphsDir;
+
+       if (!optionSyncStats)
+       {
+               g_log_set_handler(NULL, G_LOG_LEVEL_DEBUG, nullLog, NULL);
+       }
+
+       // Identify and initialize matching and analysis modules
+       syncState->matchingData= NULL;
+       syncState->analysisData= NULL;
+
+       g_assert(g_queue_get_length(&matchingModules) == 1);
+       syncState->matchingModule= (MatchingModule*)
+               g_queue_peek_head(&matchingModules);
+
+       graphsStream= NULL;
+       if (syncState->graphs)
+       {
+               // Create the graph directory right away in case the module initialization
+               // functions have something to write in it.
+               cwd= changeToGraphDir(syncState->graphs);
+
+               if (syncState->matchingModule->writeMatchingGraphsPlots != NULL)
+               {
+                       if ((graphsFp= open("graphs.gnu", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR |
+                                       S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH
+                                       | S_IWOTH | S_IXOTH)) == -1)
+                       {
+                               g_error(strerror(errno));
+                       }
+                       if ((graphsStream= fdopen(graphsFp, "w")) == NULL)
+                       {
+                               g_error(strerror(errno));
+                       }
+               }
+
+               retval= chdir(cwd);
+               if (retval == -1)
+               {
+                       g_error(strerror(errno));
+               }
+               free(cwd);
+       }
+
+       syncState->matchingModule->initMatching(syncState);
+       syncState->analysisModule->initAnalysis(syncState);
+
+       // Process traceset
+       processEvents(syncState, testCase);
+
+       factors= syncState->matchingModule->finalizeMatching(syncState);
+
+       // Write graphs file
+       if (graphsStream != NULL)
+       {
+               unsigned int i, j;
+
+               fprintf(graphsStream,
+                       "#!/usr/bin/gnuplot\n\n"
+                       "#set terminal postscript eps color size 8in,6in\n");
+
+               // Cover the upper triangular matrix, i is the reference node.
+               for (i= 0; i < syncState->traceNb; i++)
+               {
+                       for (j= i + 1; j < syncState->traceNb; j++)
+                       {
+                               long pos;
+
+                               fprintf(graphsStream,
+                                       "\n#set output \"%03d-%03d.eps\"\n"
+                                       "plot \\\n", i, j);
+
+                               syncState->matchingModule->writeMatchingGraphsPlots(graphsStream,
+                                       syncState, i, j);
+
+                               // Remove the ", \\\n" from the last graph plot line
+                               fflush(graphsStream);
+                               pos= ftell(graphsStream);
+                               if (ftruncate(fileno(graphsStream), pos - 4) == -1)
+                               {
+                                       g_error(strerror(errno));
+                               }
+                               if (fseek(graphsStream, 0, SEEK_END) == -1)
+                               {
+                                       g_error(strerror(errno));
+                               }
+
+                               fprintf(graphsStream,
+                                       "\nset output \"%1$03d-%2$03d.eps\"\n"
+                                       "set title \"\"\n"
+                                       "set xlabel \"Clock %1$u\"\n"
+                                       "set xtics nomirror\n"
+                                       "set ylabel \"Clock %2$u\"\n"
+                                       "set ytics nomirror\n"
+                                       "set x2label \"Clock %1$u (s)\"\n"
+                                       "set x2range [GPVAL_X_MIN / %3$.1f : GPVAL_X_MAX / %3$.1f]\n"
+                                       "set x2tics\n"
+                                       "set y2label \"Clock %2$u (s)\"\n"
+                                       "set y2range [GPVAL_Y_MIN / %3$.1f: GPVAL_Y_MAX / %3$.1f]\n"
+                                       "set y2tics\n"
+                                       "set key inside right bottom\n", i, j, freq);
+
+                               syncState->matchingModule->writeMatchingGraphsOptions(graphsStream,
+                                       syncState, i, j);
+
+                               fprintf(graphsStream, "replot\n\n"
+                                       "pause -1\n");
+                       }
+               }
+
+               if (fclose(graphsStream) != 0)
+               {
+                       g_error(strerror(errno));
+               }
+       }
+       if (optionGraphsDir)
+       {
+               free(optionGraphsDir);
+       }
+
+       if (optionSyncStats && syncState->matchingModule->printMatchingStats !=
+               NULL)
+       {
+               unsigned int i;
+
+               syncState->matchingModule->printMatchingStats(syncState);
+
+               printf("Resulting synchronization factors:\n");
+               for (i= 0; i < syncState->traceNb; i++)
+               {
+                       Factors* traceFactors;
+
+                       traceFactors= &g_array_index(factors, Factors, i);
+                       printf("\ttrace %u drift= %g offset= %g\n", i,
+                               traceFactors->drift, traceFactors->offset);
+               }
+       }
+
+       syncState->matchingModule->destroyMatching(syncState);
+       syncState->analysisModule->destroyAnalysis(syncState);
+
+       free(syncState);
+
+       if (optionSyncStats)
+       {
+               gettimeofday(&endTime, 0);
+               retval= getrusage(RUSAGE_SELF, &endUsage);
+
+               timeDiff(&endTime, &startTime);
+               timeDiff(&endUsage.ru_utime, &startUsage.ru_utime);
+               timeDiff(&endUsage.ru_stime, &startUsage.ru_stime);
+
+               printf("Synchronization time:\n");
+               printf("\treal time: %ld.%06ld\n", endTime.tv_sec, endTime.tv_usec);
+               printf("\tuser time: %ld.%06ld\n", endUsage.ru_utime.tv_sec,
+                       endUsage.ru_utime.tv_usec);
+               printf("\tsystem time: %ld.%06ld\n", endUsage.ru_stime.tv_sec,
+                       endUsage.ru_stime.tv_usec);
+       }
+
+       return EXIT_SUCCESS;
+}
+
+
+/*
+ * Print information about program options and arguments.
+ *
+ * Args:
+ *   programName:  name of the program, as contained in argv[0] for example
+ */
+static void usage(const char* const programName)
+{
+       GString* analysisModulesNames;
+
+       analysisModulesNames= g_string_new("");
+       g_queue_foreach(&analysisModules, &gfAppendAnalysisName,
+               analysisModulesNames);
+       // remove the last ", "
+       g_string_truncate(analysisModulesNames, analysisModulesNames->len - 2);
+
+       printf(
+               "%s [options] <test file>\n"
+               "Options:\n"
+               "\t-s, --sync-stats                 Print statistics and debug messages\n"
+               "\t-g, --sync-graphs[=OUPUT_DIR]    Generate graphs\n"
+               "\t-a, --sync-analysis=MODULE_NAME  Specify which module to use for analysis\n"
+               "\t                                 Available modules: %s\n",
+               programName, analysisModulesNames->str);
+
+       g_string_free(analysisModulesNames, TRUE);
+}
+
+
+/*
+ * Calculate the elapsed time between two timeval values
+ *
+ * Args:
+ *   end:          end time, result is also stored in this structure
+ *   start:        start time
+ */
+static void timeDiff(struct timeval* const end, const struct timeval* const start)
+{
+               if (end->tv_usec >= start->tv_usec)
+               {
+                       end->tv_sec-= start->tv_sec;
+                       end->tv_usec-= start->tv_usec;
+               }
+               else
+               {
+                       end->tv_sec= end->tv_sec - start->tv_sec - 1;
+                       end->tv_usec= end->tv_usec - start->tv_usec + 1e6;
+               }
+}
+
+
+/*
+ * A GCompareFunc for g_slist_find_custom()
+ *
+ * Args:
+ *   a:            AnalysisModule*, element's data
+ *   b:            char*, user data to compare against
+ *
+ * Returns:
+ *   0 if the analysis module a's name is b
+ */
+static gint gcfCompareAnalysis(gconstpointer a, gconstpointer b)
+{
+       const AnalysisModule* analysisModule;
+       const char* name;
+
+       analysisModule= (const AnalysisModule*)a;
+       name= (const char*)b;
+
+       return strncmp(analysisModule->name, name, strlen(analysisModule->name) +
+               1);
+}
+
+
+/*
+ * Change to the directory used to hold graphs. Create it if necessary.
+ *
+ * Args:
+ *   graph:        name of directory
+ *
+ * Returns:
+ *   The current working directory before the execution of the function. The
+ *   string must be free'd by the caller.
+ */
+char* changeToGraphDir(char* const graphs)
+{
+       int retval;
+       char* cwd;
+
+       cwd= getcwd(NULL, 0);
+       if (cwd == NULL)
+       {
+               g_error(strerror(errno));
+       }
+       while ((retval= chdir(graphs)) != 0)
+       {
+               if (errno == ENOENT)
+               {
+                       retval= mkdir(graphs, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP |
+                               S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH);
+                       if (retval != 0)
+                       {
+                               g_error(strerror(errno));
+                       }
+               }
+               else
+               {
+                       g_error(strerror(errno));
+               }
+       }
+
+       return cwd;
+}
+
+
+/*
+ * A GFunc for g_queue_foreach()
+ *
+ * Concatenate analysis module names.
+ *
+ * Args:
+ *   data:         AnalysisModule*
+ *   user_data:    GString*, concatenated names
+ */
+static void gfAppendAnalysisName(gpointer data, gpointer user_data)
+{
+       g_string_append((GString*) user_data, ((AnalysisModule*) data)->name);
+       g_string_append((GString*) user_data, ", ");
+}
+
+
+/*
+ * Read trace number from the test case stream. The trace number should be the
+ * first non-comment line and should be an unsigned int by itself on a line.
+ *
+ * Args:
+ *   testCase:     test case stream
+ *
+ * Returns:
+ *   The trace number
+ */
+static unsigned int readTraceNb(FILE* testCase)
+{
+       unsigned int result;
+       int retval;
+       char* line= NULL;
+       size_t len;
+       char tmp;
+
+       skipCommentLines(testCase);
+       retval= getline(&line, &len, testCase);
+       if (retval == -1)
+       {
+               if (feof(testCase))
+               {
+                       g_error("Unexpected end of file while looking for number of traces");
+               }
+               else
+               {
+                       g_error(strerror(errno));
+               }
+       }
+       if (line[retval - 1] == '\n')
+       {
+               line[retval - 1]= '\0';
+       }
+
+       retval= sscanf(line, " %u %c", &result, &tmp);
+       if (retval == EOF || retval != 1)
+       {
+               g_error("Error parsing test file while looking for number of traces, line was '%s'", line);
+
+               // Not really needed but avoids warning from gcc
+               abort();
+       }
+
+       return result;
+}
+
+
+/*
+ * Advance testCase stream over empty space, empty lines and lines that begin
+ * with '#'
+ *
+ * Args:
+ *   testCase:     test case stream
+ */
+static void skipCommentLines(FILE* testCase)
+{
+       int firstChar;
+       ssize_t retval;
+       char* line= NULL;
+       size_t len;
+
+       do
+       {
+               firstChar= fgetc(testCase);
+               if (firstChar == (int) '#')
+               {
+                       retval= getline(&line, &len, testCase);
+                       if (retval == -1)
+                       {
+                               if (feof(testCase))
+                               {
+                                       goto outEof;
+                               }
+                               else
+                               {
+                                       g_error(strerror(errno));
+                               }
+                       }
+               }
+               else if (firstChar == (int) '\n' || firstChar == (int) ' ')
+               {}
+               else if (firstChar == EOF)
+               {
+                       goto outEof;
+               }
+               else
+               {
+                       break;
+               }
+       } while (true);
+       retval= ungetc(firstChar, testCase);
+       if (retval == EOF)
+       {
+               g_error("Error: ungetc()");
+       }
+
+outEof:
+       if (line)
+       {
+               free(line);
+       }
+}
+
+
+/*
+ * Make up events from the messages in the test case. Dispatch those events to
+ * the matching module.
+ */
+static void processEvents(SyncState* const syncState, FILE* testCase)
+{
+       char* line= NULL;
+       size_t len;
+       int retval;
+       unsigned int addressOffset;
+       unsigned int* seq;
+
+       // Trace numbers run from 0 to traceNb - 1. addressOffset is added to a
+       // traceNum to convert it to an address.
+       addressOffset= pow(10, floor(log(syncState->traceNb - 1) / log(10)) + 1);
+
+       seq= calloc(syncState->traceNb, sizeof(unsigned int));
+
+       skipCommentLines(testCase);
+       retval= getline(&line, &len, testCase);
+       while(!feof(testCase))
+       {
+               unsigned int sender, receiver;
+               double sendTime, recvTime;
+               char tmp;
+               NetEvent* event;
+
+               if (retval == -1 && !feof(testCase))
+               {
+                       g_error(strerror(errno));
+               }
+
+               if (line[len - 1] == '\n')
+               {
+                       line[len - 1]= '\0';
+               }
+
+               retval= sscanf(line, " %u %u %lf %lf %c", &sender, &receiver,
+                       &sendTime, &recvTime, &tmp);
+               if (retval == EOF)
+               {
+                       g_error(strerror(errno));
+               }
+               else if (retval != 4)
+               {
+                       g_error("Error parsing test file while looking for data point, line was '%s'", line);
+               }
+
+               if (sender + 1 > syncState->traceNb)
+               {
+                       g_error("Error parsing test file, sender is out of range, line was '%s'", line);
+               }
+
+               if (receiver + 1 > syncState->traceNb)
+               {
+                       g_error("Error parsing test file, receiver is out of range, line was '%s'", line);
+               }
+
+               // Output event
+               event= malloc(sizeof(NetEvent));
+               event->traceNum= sender;
+               event->tsc= round(sendTime * freq);
+               event->skb= NULL;
+               event->packetKey= malloc(sizeof(PacketKey));
+               event->packetKey->ihl= 5;
+               event->packetKey->tot_len= 40;
+               event->packetKey->connectionKey.saddr= sender + addressOffset;
+               event->packetKey->connectionKey.daddr= receiver + addressOffset;
+               event->packetKey->connectionKey.source= 57645;
+               event->packetKey->connectionKey.dest= 80;
+               event->packetKey->seq= seq[sender];
+               event->packetKey->ack_seq= 0;
+               event->packetKey->doff= 5;
+               event->packetKey->ack= 0;
+               event->packetKey->rst= 0;
+               event->packetKey->syn= 1;
+               event->packetKey->fin= 0;
+
+               syncState->matchingModule->matchEvent(syncState, event, OUT);
+
+               // Input event
+               event= malloc(sizeof(NetEvent));
+               event->traceNum= receiver;
+               event->tsc= round(recvTime * freq);
+               event->skb= NULL;
+               event->packetKey= malloc(sizeof(PacketKey));
+               event->packetKey->ihl= 5;
+               event->packetKey->tot_len= 40;
+               event->packetKey->connectionKey.saddr= sender + addressOffset;
+               event->packetKey->connectionKey.daddr= receiver + addressOffset;
+               event->packetKey->connectionKey.source= 57645;
+               event->packetKey->connectionKey.dest= 80;
+               event->packetKey->seq= seq[sender];
+               event->packetKey->ack_seq= 0;
+               event->packetKey->doff= 5;
+               event->packetKey->ack= 0;
+               event->packetKey->rst= 0;
+               event->packetKey->syn= 1;
+               event->packetKey->fin= 0;
+
+               syncState->matchingModule->matchEvent(syncState, event, IN);
+
+               seq[sender]++;
+
+               skipCommentLines(testCase);
+               retval= getline(&line, &len, testCase);
+       }
+
+       free(seq);
+
+       if (line)
+       {
+               free(line);
+       }
+}
+
+
+/*
+ * A Glib log function which does nothing.
+ */
+static void nullLog(const gchar *log_domain, GLogLevelFlags log_level, const
+       gchar *message, gpointer user_data)
+{}
This page took 0.029511 seconds and 4 git commands to generate.