Add a unittest program for clock synchronization modules
[lttv.git] / lttv / lttv / sync / unittest.c
1 /* This file is part of the Linux Trace Toolkit viewer
2 * Copyright (C) 2009 Benjamin Poirier <benjamin.poirier@polymtl.ca>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License Version 2 as
6 * published by the Free Software Foundation;
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
16 * MA 02111-1307, USA.
17 */
18
19 #define _GNU_SOURCE
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <getopt.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <sys/resource.h>
31 #include <sys/stat.h>
32 #include <sys/time.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <unistd.h>
36
37 #include "sync_chain.h"
38
39
40 #ifndef g_info
41 #define g_info(format...) g_log (G_LOG_DOMAIN, G_LOG_LEVEL_INFO, format)
42 #endif
43
44
45 static void timeDiff(struct timeval* const end, const struct timeval* const start);
46 static void usage(const char* const programName);
47 static gint gcfCompareAnalysis(gconstpointer a, gconstpointer b);
48 static void gfAppendAnalysisName(gpointer data, gpointer user_data);
49 static unsigned int readTraceNb(FILE* testCase);
50 static void skipCommentLines(FILE* testCase);
51 static void processEvents(SyncState* const syncState, FILE* testCase);
52 static void nullLog(const gchar *log_domain, GLogLevelFlags log_level, const
53 gchar *message, gpointer user_data);
54
55 GQueue processingModules= G_QUEUE_INIT;
56 GQueue matchingModules= G_QUEUE_INIT;
57 GQueue analysisModules= G_QUEUE_INIT;
58
59 // time values in test case files will be scaled by this factor
60 const double freq= 1e9;
61
62
63 /*
64 * Create matching and analysis modules and feed them events read from a text
65 * file.
66 *
67 * Idealy, this would've been a processing module but sync_chain.c and
68 * ProcessingModule use some LTTV-specific types and functions. Unfortunately,
69 * there is some code duplication from sync_chain.c
70 *
71 */
72 int main(int argc, char* argv[])
73 {
74 int c;
75 extern char* optarg;
76 extern int optind, opterr, optopt;
77 bool optionSyncStats= false;
78 char* optionGraphsDir= NULL;
79 FILE* testCase= NULL;
80
81 SyncState* syncState;
82 struct timeval startTime, endTime;
83 struct rusage startUsage, endUsage;
84 GList* result;
85 char* cwd;
86 FILE* graphsStream;
87 int graphsFp;
88 GArray* factors;
89
90 int retval;
91
92 syncState= malloc(sizeof(SyncState));
93
94 g_assert(g_queue_get_length(&analysisModules) > 0);
95 syncState->analysisModule= g_queue_peek_head(&analysisModules);
96
97 do
98 {
99 int optionIndex= 0;
100
101 static struct option longOptions[]=
102 {
103 {"sync-stats", no_argument, 0, 's'},
104 {"sync-graphs", optional_argument, 0, 'g'},
105 {"sync-analysis", required_argument, 0, 'a'},
106 {0, 0, 0, 0}
107 };
108
109 c= getopt_long(argc, argv, "sg::a:", longOptions, &optionIndex);
110
111 switch (c)
112 {
113 case -1:
114 case 0:
115 break;
116
117 case 's':
118 if (!optionSyncStats)
119 {
120 gettimeofday(&startTime, 0);
121 getrusage(RUSAGE_SELF, &startUsage);
122 }
123 optionSyncStats= true;
124 break;
125
126 case 'g':
127 if (optarg)
128 {
129 printf("xxx:%s\n", optarg);
130 optionGraphsDir= malloc(strlen(optarg));
131 strcpy(optionGraphsDir, optarg);
132 }
133 else
134 {
135 optionGraphsDir= malloc(20);
136 retval= snprintf(optionGraphsDir, 20, "graphs-%d",
137 getpid());
138 if (retval > 20 - 1)
139 {
140 optionGraphsDir[20 - 1]= '\0';
141 }
142 }
143 break;
144
145 case 'a':
146 printf("xxx:%s\n", optarg);
147 result= g_queue_find_custom(&analysisModules, optarg,
148 &gcfCompareAnalysis);
149 if (result != NULL)
150 {
151 syncState->analysisModule= (AnalysisModule*) result->data;
152 }
153 else
154 {
155 g_error("Analysis module '%s' not found", optarg);
156 }
157 break;
158
159 case '?':
160 usage(argv[0]);
161 abort();
162
163 default:
164 g_error("Option parse error");
165 }
166 } while (c != -1);
167
168 if (argc <= optind)
169 {
170 fprintf(stderr, "Test file unspecified\n");
171 usage(argv[0]);
172 abort();
173 }
174
175 testCase= fopen(argv[optind], "r");
176 if (testCase == NULL)
177 {
178 g_error(strerror(errno));
179 }
180
181 // Initialize data structures
182 syncState->traceNb= readTraceNb(testCase);
183
184 if (optionSyncStats)
185 {
186 syncState->stats= true;
187 }
188 else
189 {
190 syncState->stats= false;
191 }
192
193 syncState->graphs= optionGraphsDir;
194
195 if (!optionSyncStats)
196 {
197 g_log_set_handler(NULL, G_LOG_LEVEL_DEBUG, nullLog, NULL);
198 }
199
200 // Identify and initialize matching and analysis modules
201 syncState->matchingData= NULL;
202 syncState->analysisData= NULL;
203
204 g_assert(g_queue_get_length(&matchingModules) == 1);
205 syncState->matchingModule= (MatchingModule*)
206 g_queue_peek_head(&matchingModules);
207
208 graphsStream= NULL;
209 if (syncState->graphs)
210 {
211 // Create the graph directory right away in case the module initialization
212 // functions have something to write in it.
213 cwd= changeToGraphDir(syncState->graphs);
214
215 if (syncState->matchingModule->writeMatchingGraphsPlots != NULL)
216 {
217 if ((graphsFp= open("graphs.gnu", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR |
218 S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH
219 | S_IWOTH | S_IXOTH)) == -1)
220 {
221 g_error(strerror(errno));
222 }
223 if ((graphsStream= fdopen(graphsFp, "w")) == NULL)
224 {
225 g_error(strerror(errno));
226 }
227 }
228
229 retval= chdir(cwd);
230 if (retval == -1)
231 {
232 g_error(strerror(errno));
233 }
234 free(cwd);
235 }
236
237 syncState->matchingModule->initMatching(syncState);
238 syncState->analysisModule->initAnalysis(syncState);
239
240 // Process traceset
241 processEvents(syncState, testCase);
242
243 factors= syncState->matchingModule->finalizeMatching(syncState);
244
245 // Write graphs file
246 if (graphsStream != NULL)
247 {
248 unsigned int i, j;
249
250 fprintf(graphsStream,
251 "#!/usr/bin/gnuplot\n\n"
252 "#set terminal postscript eps color size 8in,6in\n");
253
254 // Cover the upper triangular matrix, i is the reference node.
255 for (i= 0; i < syncState->traceNb; i++)
256 {
257 for (j= i + 1; j < syncState->traceNb; j++)
258 {
259 long pos;
260
261 fprintf(graphsStream,
262 "\n#set output \"%03d-%03d.eps\"\n"
263 "plot \\\n", i, j);
264
265 syncState->matchingModule->writeMatchingGraphsPlots(graphsStream,
266 syncState, i, j);
267
268 // Remove the ", \\\n" from the last graph plot line
269 fflush(graphsStream);
270 pos= ftell(graphsStream);
271 if (ftruncate(fileno(graphsStream), pos - 4) == -1)
272 {
273 g_error(strerror(errno));
274 }
275 if (fseek(graphsStream, 0, SEEK_END) == -1)
276 {
277 g_error(strerror(errno));
278 }
279
280 fprintf(graphsStream,
281 "\nset output \"%1$03d-%2$03d.eps\"\n"
282 "set title \"\"\n"
283 "set xlabel \"Clock %1$u\"\n"
284 "set xtics nomirror\n"
285 "set ylabel \"Clock %2$u\"\n"
286 "set ytics nomirror\n"
287 "set x2label \"Clock %1$u (s)\"\n"
288 "set x2range [GPVAL_X_MIN / %3$.1f : GPVAL_X_MAX / %3$.1f]\n"
289 "set x2tics\n"
290 "set y2label \"Clock %2$u (s)\"\n"
291 "set y2range [GPVAL_Y_MIN / %3$.1f: GPVAL_Y_MAX / %3$.1f]\n"
292 "set y2tics\n"
293 "set key inside right bottom\n", i, j, freq);
294
295 syncState->matchingModule->writeMatchingGraphsOptions(graphsStream,
296 syncState, i, j);
297
298 fprintf(graphsStream, "replot\n\n"
299 "pause -1\n");
300 }
301 }
302
303 if (fclose(graphsStream) != 0)
304 {
305 g_error(strerror(errno));
306 }
307 }
308 if (optionGraphsDir)
309 {
310 free(optionGraphsDir);
311 }
312
313 if (optionSyncStats && syncState->matchingModule->printMatchingStats !=
314 NULL)
315 {
316 unsigned int i;
317
318 syncState->matchingModule->printMatchingStats(syncState);
319
320 printf("Resulting synchronization factors:\n");
321 for (i= 0; i < syncState->traceNb; i++)
322 {
323 Factors* traceFactors;
324
325 traceFactors= &g_array_index(factors, Factors, i);
326 printf("\ttrace %u drift= %g offset= %g\n", i,
327 traceFactors->drift, traceFactors->offset);
328 }
329 }
330
331 syncState->matchingModule->destroyMatching(syncState);
332 syncState->analysisModule->destroyAnalysis(syncState);
333
334 free(syncState);
335
336 if (optionSyncStats)
337 {
338 gettimeofday(&endTime, 0);
339 retval= getrusage(RUSAGE_SELF, &endUsage);
340
341 timeDiff(&endTime, &startTime);
342 timeDiff(&endUsage.ru_utime, &startUsage.ru_utime);
343 timeDiff(&endUsage.ru_stime, &startUsage.ru_stime);
344
345 printf("Synchronization time:\n");
346 printf("\treal time: %ld.%06ld\n", endTime.tv_sec, endTime.tv_usec);
347 printf("\tuser time: %ld.%06ld\n", endUsage.ru_utime.tv_sec,
348 endUsage.ru_utime.tv_usec);
349 printf("\tsystem time: %ld.%06ld\n", endUsage.ru_stime.tv_sec,
350 endUsage.ru_stime.tv_usec);
351 }
352
353 return EXIT_SUCCESS;
354 }
355
356
357 /*
358 * Print information about program options and arguments.
359 *
360 * Args:
361 * programName: name of the program, as contained in argv[0] for example
362 */
363 static void usage(const char* const programName)
364 {
365 GString* analysisModulesNames;
366
367 analysisModulesNames= g_string_new("");
368 g_queue_foreach(&analysisModules, &gfAppendAnalysisName,
369 analysisModulesNames);
370 // remove the last ", "
371 g_string_truncate(analysisModulesNames, analysisModulesNames->len - 2);
372
373 printf(
374 "%s [options] <test file>\n"
375 "Options:\n"
376 "\t-s, --sync-stats Print statistics and debug messages\n"
377 "\t-g, --sync-graphs[=OUPUT_DIR] Generate graphs\n"
378 "\t-a, --sync-analysis=MODULE_NAME Specify which module to use for analysis\n"
379 "\t Available modules: %s\n",
380 programName, analysisModulesNames->str);
381
382 g_string_free(analysisModulesNames, TRUE);
383 }
384
385
386 /*
387 * Calculate the elapsed time between two timeval values
388 *
389 * Args:
390 * end: end time, result is also stored in this structure
391 * start: start time
392 */
393 static void timeDiff(struct timeval* const end, const struct timeval* const start)
394 {
395 if (end->tv_usec >= start->tv_usec)
396 {
397 end->tv_sec-= start->tv_sec;
398 end->tv_usec-= start->tv_usec;
399 }
400 else
401 {
402 end->tv_sec= end->tv_sec - start->tv_sec - 1;
403 end->tv_usec= end->tv_usec - start->tv_usec + 1e6;
404 }
405 }
406
407
408 /*
409 * A GCompareFunc for g_slist_find_custom()
410 *
411 * Args:
412 * a: AnalysisModule*, element's data
413 * b: char*, user data to compare against
414 *
415 * Returns:
416 * 0 if the analysis module a's name is b
417 */
418 static gint gcfCompareAnalysis(gconstpointer a, gconstpointer b)
419 {
420 const AnalysisModule* analysisModule;
421 const char* name;
422
423 analysisModule= (const AnalysisModule*)a;
424 name= (const char*)b;
425
426 return strncmp(analysisModule->name, name, strlen(analysisModule->name) +
427 1);
428 }
429
430
431 /*
432 * Change to the directory used to hold graphs. Create it if necessary.
433 *
434 * Args:
435 * graph: name of directory
436 *
437 * Returns:
438 * The current working directory before the execution of the function. The
439 * string must be free'd by the caller.
440 */
441 char* changeToGraphDir(char* const graphs)
442 {
443 int retval;
444 char* cwd;
445
446 cwd= getcwd(NULL, 0);
447 if (cwd == NULL)
448 {
449 g_error(strerror(errno));
450 }
451 while ((retval= chdir(graphs)) != 0)
452 {
453 if (errno == ENOENT)
454 {
455 retval= mkdir(graphs, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP |
456 S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH);
457 if (retval != 0)
458 {
459 g_error(strerror(errno));
460 }
461 }
462 else
463 {
464 g_error(strerror(errno));
465 }
466 }
467
468 return cwd;
469 }
470
471
472 /*
473 * A GFunc for g_queue_foreach()
474 *
475 * Concatenate analysis module names.
476 *
477 * Args:
478 * data: AnalysisModule*
479 * user_data: GString*, concatenated names
480 */
481 static void gfAppendAnalysisName(gpointer data, gpointer user_data)
482 {
483 g_string_append((GString*) user_data, ((AnalysisModule*) data)->name);
484 g_string_append((GString*) user_data, ", ");
485 }
486
487
488 /*
489 * Read trace number from the test case stream. The trace number should be the
490 * first non-comment line and should be an unsigned int by itself on a line.
491 *
492 * Args:
493 * testCase: test case stream
494 *
495 * Returns:
496 * The trace number
497 */
498 static unsigned int readTraceNb(FILE* testCase)
499 {
500 unsigned int result;
501 int retval;
502 char* line= NULL;
503 size_t len;
504 char tmp;
505
506 skipCommentLines(testCase);
507 retval= getline(&line, &len, testCase);
508 if (retval == -1)
509 {
510 if (feof(testCase))
511 {
512 g_error("Unexpected end of file while looking for number of traces");
513 }
514 else
515 {
516 g_error(strerror(errno));
517 }
518 }
519 if (line[retval - 1] == '\n')
520 {
521 line[retval - 1]= '\0';
522 }
523
524 retval= sscanf(line, " %u %c", &result, &tmp);
525 if (retval == EOF || retval != 1)
526 {
527 g_error("Error parsing test file while looking for number of traces, line was '%s'", line);
528
529 // Not really needed but avoids warning from gcc
530 abort();
531 }
532
533 return result;
534 }
535
536
537 /*
538 * Advance testCase stream over empty space, empty lines and lines that begin
539 * with '#'
540 *
541 * Args:
542 * testCase: test case stream
543 */
544 static void skipCommentLines(FILE* testCase)
545 {
546 int firstChar;
547 ssize_t retval;
548 char* line= NULL;
549 size_t len;
550
551 do
552 {
553 firstChar= fgetc(testCase);
554 if (firstChar == (int) '#')
555 {
556 retval= getline(&line, &len, testCase);
557 if (retval == -1)
558 {
559 if (feof(testCase))
560 {
561 goto outEof;
562 }
563 else
564 {
565 g_error(strerror(errno));
566 }
567 }
568 }
569 else if (firstChar == (int) '\n' || firstChar == (int) ' ')
570 {}
571 else if (firstChar == EOF)
572 {
573 goto outEof;
574 }
575 else
576 {
577 break;
578 }
579 } while (true);
580 retval= ungetc(firstChar, testCase);
581 if (retval == EOF)
582 {
583 g_error("Error: ungetc()");
584 }
585
586 outEof:
587 if (line)
588 {
589 free(line);
590 }
591 }
592
593
594 /*
595 * Make up events from the messages in the test case. Dispatch those events to
596 * the matching module.
597 */
598 static void processEvents(SyncState* const syncState, FILE* testCase)
599 {
600 char* line= NULL;
601 size_t len;
602 int retval;
603 unsigned int addressOffset;
604 unsigned int* seq;
605
606 // Trace numbers run from 0 to traceNb - 1. addressOffset is added to a
607 // traceNum to convert it to an address.
608 addressOffset= pow(10, floor(log(syncState->traceNb - 1) / log(10)) + 1);
609
610 seq= calloc(syncState->traceNb, sizeof(unsigned int));
611
612 skipCommentLines(testCase);
613 retval= getline(&line, &len, testCase);
614 while(!feof(testCase))
615 {
616 unsigned int sender, receiver;
617 double sendTime, recvTime;
618 char tmp;
619 NetEvent* event;
620
621 if (retval == -1 && !feof(testCase))
622 {
623 g_error(strerror(errno));
624 }
625
626 if (line[len - 1] == '\n')
627 {
628 line[len - 1]= '\0';
629 }
630
631 retval= sscanf(line, " %u %u %lf %lf %c", &sender, &receiver,
632 &sendTime, &recvTime, &tmp);
633 if (retval == EOF)
634 {
635 g_error(strerror(errno));
636 }
637 else if (retval != 4)
638 {
639 g_error("Error parsing test file while looking for data point, line was '%s'", line);
640 }
641
642 if (sender + 1 > syncState->traceNb)
643 {
644 g_error("Error parsing test file, sender is out of range, line was '%s'", line);
645 }
646
647 if (receiver + 1 > syncState->traceNb)
648 {
649 g_error("Error parsing test file, receiver is out of range, line was '%s'", line);
650 }
651
652 // Output event
653 event= malloc(sizeof(NetEvent));
654 event->traceNum= sender;
655 event->tsc= round(sendTime * freq);
656 event->skb= NULL;
657 event->packetKey= malloc(sizeof(PacketKey));
658 event->packetKey->ihl= 5;
659 event->packetKey->tot_len= 40;
660 event->packetKey->connectionKey.saddr= sender + addressOffset;
661 event->packetKey->connectionKey.daddr= receiver + addressOffset;
662 event->packetKey->connectionKey.source= 57645;
663 event->packetKey->connectionKey.dest= 80;
664 event->packetKey->seq= seq[sender];
665 event->packetKey->ack_seq= 0;
666 event->packetKey->doff= 5;
667 event->packetKey->ack= 0;
668 event->packetKey->rst= 0;
669 event->packetKey->syn= 1;
670 event->packetKey->fin= 0;
671
672 syncState->matchingModule->matchEvent(syncState, event, OUT);
673
674 // Input event
675 event= malloc(sizeof(NetEvent));
676 event->traceNum= receiver;
677 event->tsc= round(recvTime * freq);
678 event->skb= NULL;
679 event->packetKey= malloc(sizeof(PacketKey));
680 event->packetKey->ihl= 5;
681 event->packetKey->tot_len= 40;
682 event->packetKey->connectionKey.saddr= sender + addressOffset;
683 event->packetKey->connectionKey.daddr= receiver + addressOffset;
684 event->packetKey->connectionKey.source= 57645;
685 event->packetKey->connectionKey.dest= 80;
686 event->packetKey->seq= seq[sender];
687 event->packetKey->ack_seq= 0;
688 event->packetKey->doff= 5;
689 event->packetKey->ack= 0;
690 event->packetKey->rst= 0;
691 event->packetKey->syn= 1;
692 event->packetKey->fin= 0;
693
694 syncState->matchingModule->matchEvent(syncState, event, IN);
695
696 seq[sender]++;
697
698 skipCommentLines(testCase);
699 retval= getline(&line, &len, testCase);
700 }
701
702 free(seq);
703
704 if (line)
705 {
706 free(line);
707 }
708 }
709
710
711 /*
712 * A Glib log function which does nothing.
713 */
714 static void nullLog(const gchar *log_domain, GLogLevelFlags log_level, const
715 gchar *message, gpointer user_data)
716 {}
This page took 0.043699 seconds and 4 git commands to generate.