The ASP Test Program, asptest.c

The asptest program is listed as a separate protocol in the graph.comp file (next page). The program performs a "ping-pong" test, bouncing ASP messages back and forth between client and server.

Since there is no separate include file, we start with a whole bunch of defines.

/*
 * $RCSfile: asptest.c,v $
 *
 * x-kernel v3.3
 *
 * Copyright (c) 1996,1993,1991,1990  Arizona Board of Regents
 */

#ifndef SUNOS
#include 
#endif
#include 
#include 
#include "xkernel.h"
#include "asp.h"
#include "ip.h"
#include "x_stdio.h"

#define argPrefix(str) ((!strncmp(arg, str, strlen(str))) && strlen(arg)>strlen(str))
#define argEq(str)     (!strcmp(arg, str))
The argPrefix macro returns TRUE if "str" is a proper prefix of "arg". The argEq macro returns TRUE if "str" is identical to "arg".
#define HOST_TYPE      IPhost

#ifndef STR2HOST
#define STR2HOST str2ipHost
#endif

#define TRACE_VAR traceasptestp
This controls the built-in xkernel tracing mechanism.

The existing code does not set the value of TRACE_VAR, presumably because they expect us to do it. [It is set in graph.comp by adding, for example, trace=TR_MAJOR_EVENTS after a tab just before the semicolon on the line for the protocol that one wants to trace; see section 12.2 in the x-kernel Programmer's manual for more information.] I suggest setting to either TR_MAJOR_EVENTS (report every open, close, etc.) or TR_FULL_TRACE (every subroutine entry and exit).

#define PROT_STRING "asp"
#define TIMES 5
#define DELAY 3
#define SPIN_DELAY 1
#define FAILURE_LIMIT 3
The above four parameters control the "ping-pong" test below.
typedef struct {
    Sessn    lls;
    Sessn    controlSessn;
    Msg      savedMsg;
    XkHandle clientPushResult;
    XkHandle serverPushResult;
    int      done, idle;
    int      clientReceived;
    XTime    starttime, endtime;
} PState;
The "Pstate" structure represents the state of the asptest "protocol." We'll be alert to the usage of these state values later.
static HOST_TYPE ServerAddr = { 0, 0, 0, 0 };
static HOST_TYPE ClientAddr = { 0, 0, 0, 0 };
ServerAddr and ClientAddr will be overwritten in asptest_init() below.
static HOST_TYPE myHost;
static ASPport   serverPort = 1789;
static ASPport   clientPort = 1865;

static int  serverParam, clientParam;
static char *serverString;

/* Sizes of messages sent, in bytes */
#define NUM_TESTS 11
static int size[] = { 1, 200, 400, 600, 800, 1024, 2048, 4096, 8192, 16384, 32768 };
static int trips = 100;
I find it helpful when debugging to reduce NUM_TESTS to a smaller value such as 1 or 2.

The idea of the "ping-pong" test is to measure the time taken for 100 round trips using each of the message sizes in size[].

int TRACE_VAR;

static void     client(Event, void *);
static void     server(Event, void *);
static XkReturn clientDemux(Protl, Sessn, Msg *);
static int      runTest(Protl, int, int);
static XkReturn serverDemux(Protl, Sessn, Msg *);
static void     processOptions(void);
static XkReturn closeDone(Sessn);
static XkReturn customOpenDone(Protl, Protl, Sessn, Protl);

int asptest_init(Protl);

int asptest_init(Protl self)
This is automatically built into the xkernel and called to initialize the asptest "protocol" object. (Note that there is no main function in this file or anywhere in the asp source code.)

This function starts another process to run the client or server version of asptest, depending on whether the "-c" or "-s" flag was entered on the command line.

{
    Protl  llp;
    PState *ps;

    processOptions();
processOptions() reads the command line and gets
    printf("%s timing test\n", PROT_STRING);
    llp = xGetProtlDown(self, 0);
    if (llp == ERR_PROTL)
        Kabort("Test protocol has no lower protocol");
    xControlProtl(xGetProtlDown(self, 0), GETMYHOST, (char *)&myHost,
		  sizeof(HOST_TYPE));
    ps = X_NEW(PState);
    bzero((char *)ps, sizeof(PState));
    self->state = (VOID *)ps;

    if (serverParam)
        evDetach(evSchedule(server, self, 0));
If we are a server, we use the event mechanism to start a server process.

The function "server" will be called with argument "self" at time 0 microseconds in the future.

The evDetach() routine throws away the event handle generated by evSchedule, so there is no way to cancel the event.

    else if (clientParam) {
        STR2HOST(&ServerAddr, serverString);
        ClientAddr = myHost;
        evDetach(evSchedule(client, self, 0));
    }
If we are a client, we use the event mechanism to start a client process.
    return 0;
}

static void
processOptions()
This is called by asptest_init() to read the command line, which has been stuck into globalArgv[] by the xkernel.
{
    int  i;
    char *arg;

    for (i = 1; i < globalArgc; i++) {
        arg = globalArgv[i];
        if (argEq("-s"))
	    serverParam = 1;
        else if (argPrefix("-c")) {
	    clientParam = 1;
	    serverString = arg + 2;
	}
        else if (argEq("-c")) {
	    clientParam = 1;
	    serverString = globalArgv[i+1];
	    i++;
        }
        else if (argPrefix("-trips="))
	    sscanf(arg + strlen("-trips="), "%d", &trips);
        else if (argEq ("-trips")) {
	    sscanf(globalArgv[i+1], "%d", &trips);
	    i++;
	}
    }
}

/*
 *  Server startup routine
 */
static void
server(Event ev, VOID *foo)
This is called by asptest_init() in case the -s flag appears on the command line. As called, "foo" is the asptest protocol object.

Notice that the first argument is the event handle which was generated by evSchedule() in asptest_init(). This argument is unused -- it is required by the event scheduling conventions.

The roles of the asptest client and server are as follows:

{
    Part   p;
    Protl  myProtl = (Protl)foo;
    char   buf[256];
    time_t t = time(0);

    printf("%s", ctime(&t));
#ifdef SUNOS
    gethostname(buf, 255);
#else
    sysinfo(SI_HOSTNAME, buf, 255);
#endif
    printf("I am the server, running on %s\n\n", buf);

    /* Since we are a test protocol, need to fill out own method table. */
    myProtl->demux = serverDemux;
    myProtl->opendone = customOpenDone;
    myProtl->closedone = closeDone;

    partInit(&p, 1);
    partPush(p, ANY_HOST, 0);
    partPush(p, &serverPort, sizeof(ASPport));
Create a participant list p with one entry, which will be the address of the server ASP port.

The single entry p is itself a stack of addresses. Onto this stack we push:

  1. the pseudo-address ANY_HOST, whose special character is indicated by the 0 length field. This is the IP part of the address.
  2. the server port address, defined in the declarations at the top of this file.
The server doesn't need to know its own IP address -- it just sits and waits for incoming messages from the client. Compare this with the client routine just below.
    if (xOpenEnable(myProtl, myProtl, xGetProtlDown(myProtl, 0), &p) ==
	XK_FAILURE) {
        printf("%s test server can't openenable lower protocol\n",
	       PROT_STRING);
    }
    else
        printf("%s test server done with xopenenable\n", PROT_STRING);
}
Now we call aspopenenable() with the participant list p. This completes the server startup routine -- the next thing that will happen is an incoming packet from the client, which will trigger the Demux routine.
/*
 * Client initialization routine
 */
static void
client(Event ev, VOID *foo)
This routine is called from asptest_init() if the "-c" flag was entered on the command line. The "ev" and "foo" parameters have the same meaning as in server() just above.
{
    Protl  myProtl = (Protl)foo;
    PState *ps;
    char   buf[256];
    time_t t = time(0);
    int    i;
    Part   p[2];

    printf("%s", ctime(&t));
#ifdef SUNOS
    gethostname(buf, 255);
#else
    sysinfo(SI_HOSTNAME, buf, 255);
#endif
    printf("I am the client, running on %s\n", buf);
    printf("\nRunning ASP ping-pong test\n");
    printf("Each message makes %d round trips\n\n", trips);

    ps = (PState *)myProtl->state;

    /* Since we are a test protocol, need to fill out own method table. */
    myProtl->demux = clientDemux;

    partInit(p, 2);
    partPush(p[0], &ServerAddr, sizeof(IPhost));
    partPush(p[0], &serverPort, sizeof(ASPport));
    partPush(p[1], ANY_HOST, 0);
    partPush(p[1], &clientPort, sizeof(ASPport));
The client participant list needs two addresses: its own and the server's. (Recall that the server participant list needs only the server adddress itself. The server doesn't care where the client is, because it is the job of the client to open the communication.)

Again, each participant address consists of an IP address (the "host address") together with an ASP port address which is pushed on top of it.

    ps->lls = xOpen(myProtl, myProtl, xGetProtlDown(myProtl, 0), p);
    if (ps->lls == ERR_SESSN) {
        printf("%s test: open failed!\n", PROT_STRING);
        Kabort("End of test");
    }

    customOpenDone(myProtl, xGetProtlDown(myProtl, 0), ps->lls, myProtl);
The openDone() routine just prints out a trace message.

Finally, the client initiates the ping-pong tests below.

    for (i = 0; i < NUM_TESTS; i++)
        runTest(myProtl, size[i], i);
    xClose(ps->lls);

    fprintf(stderr, "End of test\n");
}

static
#if defined(__GNUC__)
inline
#endif
void checkHandle(h, str)
XkHandle h;
char     *str;
The checkHandle routine is used in runTest(), clientDemux(), and serverDemux() to abort the test in case of an error return. The parameter "h" is returned from a Push operation, attempting to send a message to the remote ASP.
{
    switch (h) {
        case XMSG_ERR_HANDLE:
        case XMSG_ERR_WOULDBLOCK:
	    sprintf(errBuf, "%s returns error handle %d", str, h);
	    Kabort(errBuf);
        default:
	    break;
    }
}

int runTest(self, len, testNumber)
Protl self;
int   len;
int   testNumber;
The runTest routine is called by client() to run one set of TIMES ping-pong tests. Each ping-pong test consists of firing one message of length "len" at the server. The clientDemux() and serverDemux() routines will bounce message back and forth until the trips limit "trips" is reached.
{
    Msg    msg;
    PState *ps = (PState *)self->state;
    XTime  total;
    int    test, failures;

    xAssert(ps);
The xAssert() routine will abort the program if ps is NULL.
    msgConstructAllocate(&ps->savedMsg, len);
Create an empty test message of length "len" bytes. We do not bother to fill in the contents.
    for (test = 0; test < TIMES; test++) {
        printf("Test %d, run %d ...\n", testNumber, test);
        printf("%d trips using msg length: %d\n", trips, len);

        ps->clientReceived = 0;
        ps->done = 0;
        failures = 0;
        xGetTime(&(ps->starttime));

        msgConstructCopy(&msg, &ps->savedMsg);
        ps->clientPushResult = xPush(ps->lls, &msg);
        checkHandle(ps->clientPushResult, "client push");
        msgDestroy(&msg);

        /* Wait until test is completed. */
        do {
	    ps->idle = 1;
	    Delay(SPIN_DELAY * 1000);
	    if (ps->idle) {
	        failures++;
	        if (failures >= FAILURE_LIMIT)
		    break;
	    }
	} while (!ps->done);
The protocol state members ps->idle and ps->done are set by the clientDemux() routine below:
        if (!ps->done) {
	    printf("\nTest failed after %d trips... terminating\n\n",
		   ps->clientReceived);
	    continue;
	}

        xSubTime(&total, ps->endtime, ps->starttime);
        printf("\nTime for test %d, run %d: %6d.%06d\n\n", testNumber, test,
	       total.sec, total.usec);

        Delay(DELAY * 1000);
    }

    msgDestroy(&ps->savedMsg);

    return 0;
}

XkReturn
serverDemux(self, lls, dg)
Protl self;
Sessn lls;
Msg   *dg;
The serverDemux function is called from xkernel when a message comes in. It simply echoes characters back to the client, which is responsible for counting them.

{
    PState *ps = (PState *)self->state;
    static int c = 1;

    xIfTrace(asptestp, TR_MAJOR_EVENTS) {
        putchar('.');
        if (!(c++ % 50))
	    putchar('\n');
    }

    /* Echo packet back to client */
    ps->serverPushResult = xPush(lls, dg);
    checkHandle(ps->serverPushResult, "server push");

    return XK_SUCCESS;
}

static XkReturn
clientDemux(self, lls, dg)
Protl self;
Sessn lls;
Msg   *dg;
The clientDemux function is similar to serverDemux(), except
{
    PState *ps = (PState *)self->state;
    Msg    tmpMsg;
    static int c = 1;

    ps->clientReceived++;
    ps->idle = 0;
    xIfTrace(asptestp, TR_MAJOR_EVENTS) {
        putchar('.');
        if (!(c++ % 50))
	    putchar('\n');
    }

    if (ps->clientReceived < trips) {
        /*
         * We need to construct a copy of the original message rather than
	 * just loop back the incoming message to avoid an increasingly
	 * fragmented message structure in the case of loopback.
         */

        msgConstructCopy(&tmpMsg, &ps->savedMsg);
        ps->clientPushResult = xPush(lls, &tmpMsg);

        checkHandle(ps->clientPushResult, "client push");
        msgDestroy(&tmpMsg);
    }
    else {
        xGetTime(&ps->endtime);
        ps->done = 1;
    }
    return XK_SUCCESS;
}

static XkReturn
closeDone(lls)
Sessn lls;
closeDone is automatically called when an ASP close occurs. Note that the server ASP won't be closed, as it has no idea when the last test message is sent.
{
    xTrace2(asptestp, TR_MAJOR_EVENTS, "\n%s test -- closedone (%x) called",
	    PROT_STRING, lls);
    if (serverParam)
        xClose(lls);
    return XK_SUCCESS;
}

static XkReturn
customOpenDone(self, llp, s, hlpType)
Protl self, llp, hlpType;
Sessn s;
The openDone() routine is automatically called from xkernel when a port has been passively opened, and then later an active open succeeds. Accordingly, this will only be called on the server side.
{
    xTrace0(asptestp, TR_MAJOR_EVENTS, "asp test program openDone");
    if (serverParam)
        xDuplicate(s);
    return XK_SUCCESS;
}

This ends the commented walk-through of the ASP protocol.