Must AND VisAndOr BIDIRECTIONAL COMMUNICATION PROTOCOL ------------------------------------------------------ (SICS / UPM) This is a protocol to communicate the time between two visualization programs (VisAndOr and Must or actually between two copies of VisAndOr or two copies of Must). The idea is to have both .mt (muse) and .vt (visandor) files for the same execution. Then start one VisAndOr and one Must and have them work on their respective traces but communicating the "current" time to each other. This is done generally by converting from one format (which is the native format of the system being run) to the other. For example, one can generate a Must (.mt file) trace from Muse and then generate from it a file in visandor format (.vt file) from it (there is a simple program --"mtvt"-- for doing this). It does not make sense to visualize traces from andprolog on Must since it only understands or-parallelism. Both programs must accept -master and -slave switches. Starting with a -master switch means that during execution time stamps are written on the standard output (as a result of normal execution, in the case of Must, or under the users's request by hitting the right button at some point of the picture, for VisAndOr), while -slave means that time stamps are read from standard input and the program reacts accordingly (moving to that point in Must and drawing a horizontal line depicting that time in VisAndOr). Each program mus receive and respond to the queries that the other program sends to it. A simple use of the protocol is to have one program control the other one as in the following example: > visandor -master x.vt | must -slave x.mt An interesting use is to make a replay: > must -master x.mt > file > must -slave x.mt < file Furthermore, bidirectional communication is also possible and the resulting system seems to be very useful in practice. To this end Roland Karlsson has implemented a program that makes bidirectional communication via standard input and output possible (bi.c). This is how Must and VisAndOr are combined: > bi visandor -master -slave file.vt + must -tall -master -slave file.mt Two Must programs can also mutually control each other. (Roland says it was hard to avoid oscillations, but now it works.) It is called thus: > bi must -master -slave x.mt + must -master -slave x.mt Also two VisAndOr programs can mutually control each other. It is called thus: > bi visandor -master -slave x.mt + visandor -master -slave x.mt The protocol (based on ascii) is as follows: * A (completely) empty line is ignored. * The slave must stop being a slave at EOF. All other lines are interpreted according to the first character. [ Ignore line. (All other outputs from Must start with [.) t The rest of the line is treated as a trace number. u The rest of the line is treated as the time in micro seconds. m The rest of the line is treated as the time in milli seconds. s The rest of the line is treated as the time in seconds. q Quit -- both programs finish execution. Other The whole line is treated as the time in micro seconds. All times are (atof()/%f) floating point numbers. The trace number is an (atoi()/%u) unsigned integer. Ignoring EOF instead of interpreting it as quit is very useful. You can then do scripts like: must -slave checker_detecto.symmetry.15.mt << End t 86128 End Here is an example how output from VisAndOr can look: ------------------------------------------------------------------------ u 119802 u 119802 u 119802 u 176851 u 230096 u 276686 u 147376 u 236752 u 351800 u 223441 q ------------------------------------------------------------------------ Output from Must is very similar: ------------------------------------------------------------------------ u 2750042 u 5129672 u 3224225 u 19732520 u 27892375 u 17828123 u 17901510 u 18006723 q ------------------------------------------------------------------------ Here is the code for reading from both the X input and the standard output. Except for the oscillation problem, this was the only tricky part in the implementation. We are including both the Madrid and the SICS code. The SICS code, for InterViews g++, was written by Roland Carlsson. It uses select() to wait for events on both the X input and the standard input. In Madrid, Manue Carro wrote a C / C++ version. This is the SICS version. The Madrid version follows. *************************************************************************** THIS IS THE SICS VERSION: *************************************************************************** extern "C" int select(int,fd_set*,fd_set*,fd_set*,struct timeval*); // Waiting for input on stdin (if slave) or the X window boolean MuseTrace::Poll(){ if (CheckQueue()>0) return false; if (slave&&stdin->_cnt>0) return true; for(;;){ int d=world->Fileno(); // Get X input file descriptor int m=max(0+1,d+1); fd_set ready; FD_ZERO(&ready); if (slave) FD_SET(0,&ready); FD_SET(d,&ready); if(select(m, &ready, 0, 0, nil)<1) { fprintf(stderr,"Select error\n"); exit(1); } if(FD_ISSET(d,&ready)) return false; if(slave&&FD_ISSET(0,&ready)) return true; } } // Event loop of the application void MuseTrace::Run(){ Event e; // This loop alternately steps through the tracefile and reads user events do { while (runstate==skipping) Step(e); if (runstate==running) Step(e); if (runstate!=running || Check()){ if (Poll()){ if (slave) { char str[1000]; if (!gets(str)) slave=false; else { if (str[0]!=0 && str[0]!='[' slave_skip=true; if (!timebox->GoToFormatted(e,str)) break; } } } else{ while(Check() || CheckQueue()){ Read(e); e.target->Handle(e); } } speed=round((1.0-speedvalue)*10000.0); } } while (e.target); } boolean TimeSlider::GoToFormatted(Event& e,char* str) { if (str[0]!=0 && str[0]!='[') { switch(str[0]) { case 't': GoTo(e,atoi(str+1)); break; case 'u': GoToTime(e,(int)atof(str+1)); break; case 'm': GoToTime(e,(int)(atof(str+1)*1000)); break; case 's': GoToTime(e,(int)(atof(str+1)*1000000)); break; case 'q': return false; default: GoToTime(e,(int)atof(str)); } } return true; } CheckQueue() Check if there is something in the X event buffer. Check() Check if there are X events to read on the X input. Read() Read one X event (from the X buffer or the X input). stdin->_cnt>0 Check if there is something in the stdin buffer. gets() Read one line from stdin. Step() Process one trace event. *************************************************************************** THIS IS THE MADRID VERSION: *************************************************************************** #include #include #include #include #include #include #include #include #include #include #include /* Some global variables and constants are declared here in vp.h */ #include "vp.h" #define FALSE 0 #define TRUE 1 #define STDIN 0 /* Asynchronous event detection in X and stdin. November 1991, by Manuel Carro. Based on code from the must visualization tool. MCL */ extern Display *dpy; /* We detect if the first incoming event comes from the X system or from the standard input. The trick is to get the X socket file descriptor and the stdin file descriptor and select the first with data ready. TRUE is returned if the event comes from strdin. */ int stdin_ready() { fd_set file_in; XEvent event; long readables; int socket_fd; int max_files; if (!slave || XPending(dpy) > 0) return FALSE; if (slave && stdin->_cnt > 0) return TRUE; socket_fd = dpy->fd; max_files = socket_fd > STDIN ? socket_fd + 1 : STDIN + 1; for (;;){ FD_ZERO(&file_in); FD_SET(socket_fd, &file_in); FD_SET(STDIN, &file_in); if (select(max_files, &file_in, NULL, NULL, NULL) <0){ fprintf(stderr, "Select error\n"); exit (1); } if (FD_ISSET(socket_fd, &file_in)) return FALSE; if (FD_ISSET(STDIN, &file_in)) return TRUE; } } /* This function reads from our master. Must be called when stdin_ready returns 1. Messages are read from standard input. The following codes are returned: NUMBER_READ: The number will be stored in the double pointed to by time. NOTHING_READ: empty or invalid line. END_READ: This will cause visandor to leave the slave condition. EXIT_READ: This notifies the exit condition. Application must quit. */ int Read_time(time) double *time; { char str[1000]; int sleep_time; int code = NOTHING_READ; double atof(), mult = 1.0; if (gets(str) == NULL) return END_READ; else switch(*str){ case 'z': sleep((int)atof(str + 1)); break; case 's': mult *= 1000.0; case 'm': mult *= 1000.0; case 'u': *time = atof(str + 1) * mult; code = NUMBER_READ; break; case 'q': code = EXIT_READ; break; case '[': break; default: /* We look for a number. */ *time = atof(str); if (*time != 0.0 || *str == '0') code = NUMBER_READ; } return code; } /* These two functions communicate with our slave. */ /* Write in stdout */ void Write_time(t) double t; { printf("u %.0lf\n", t); fflush(stdout); } /* Notifies the end of the marriage. */ void Exit() { printf("q\n"); fflush(stdout); }