chiark / gitweb /
uslip: New options for flooding tripe.
authorMark Wooding <mdw@distorted.org.uk>
Sat, 20 Dec 2008 11:39:32 +0000 (11:39 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Sat, 20 Dec 2008 11:39:32 +0000 (11:39 +0000)
I have a bug to hunt which manifests under heavy load.

uslip/tripe-uslip.1.in
uslip/uslip.c

index 16b0a7e..47268f4 100644 (file)
@@ -38,7 +38,7 @@ tripe-uslip \- fake SLIP interface for testing tripe
 .SH "SYNOPSIS"
 .
 .B tripe-uslip
 .SH "SYNOPSIS"
 .
 .B tripe-uslip
-.RB [ \-gp ]
+.RB [ \-fgps ]
 .I socket
 .
 .\"--------------------------------------------------------------------------
 .I socket
 .
 .\"--------------------------------------------------------------------------
@@ -174,6 +174,24 @@ read a packet from the socket and write it to standard output.
 Connect to
 .IR socket ,
 read a packet from standard input and write it to the socket.
 Connect to
 .IR socket ,
 read a packet from standard input and write it to the socket.
+.TP
+.B \-f, \-\-flood
+Connect to
+.IR socket ,
+and send packets as fast as possible.  The packets sent aren't very
+interesting, and there's no way to configure their contents.
+.TP
+.B \-s, \-\-sink
+Connect to
+.IR socket
+and read packets as the become available.  The packets are discarded,
+though if stdout is a terminal, a simple spinning-baton animation is
+updated once for each group of packets.  If you are flooding one end of
+a TrIPE connection, it's advisable to attach a sink to the other:
+otherwise the destination
+.B tripe-uslip
+will attempt to consume all available memory, storing incoming packets
+until someone retrieves them.
 .
 .\"--------------------------------------------------------------------------
 .SH "BUGS"
 .
 .\"--------------------------------------------------------------------------
 .SH "BUGS"
@@ -185,7 +203,10 @@ program is intended as a tool for testing the
 server.  It is not expected to be useful in production environments.  In
 particular, it intentionally imposes no limits on queue lengths or
 packet sizes, and its internals and interface (one packet per client
 server.  It is not expected to be useful in production environments.  In
 particular, it intentionally imposes no limits on queue lengths or
 packet sizes, and its internals and interface (one packet per client
-connection) are not well-suited for high performance.
+connection) are not well-suited for high performance.  That said, the
+flood option has worked well enough to expose bugs in
+.BR tripe 's
+behaviour under heavy loads.
 .PP
 If
 .B tripe-uslip
 .PP
 If
 .B tripe-uslip
index dee9f94..74a5b17 100644 (file)
@@ -47,6 +47,8 @@
 #include <netdb.h>
 
 #include <mLib/alloc.h>
 #include <netdb.h>
 
 #include <mLib/alloc.h>
+#include <mLib/bits.h>
+#include <mLib/conn.h>
 #include <mLib/dstr.h>
 #include <mLib/fdflags.h>
 #include <mLib/mdwopt.h>
 #include <mLib/dstr.h>
 #include <mLib/fdflags.h>
 #include <mLib/mdwopt.h>
@@ -54,6 +56,7 @@
 #include <mLib/report.h>
 #include <mLib/sel.h>
 #include <mLib/sub.h>
 #include <mLib/report.h>
 #include <mLib/sel.h>
 #include <mLib/sub.h>
+#include <mLib/tv.h>
 
 #include "slip.h"
 
 
 #include "slip.h"
 
@@ -136,12 +139,11 @@ static pkq_node *make_pkqnode(void *p, size_t n)
 {
   pkq_node *pn;
 
 {
   pkq_node *pn;
 
-  if (!n)
-    return (0);
+  if (!n) return (0);
   pn = CREATE(pkq_node);
   pn->next = 0;
   pn->buf = xmalloc(n);
   pn = CREATE(pkq_node);
   pn->next = 0;
   pn->buf = xmalloc(n);
-  memcpy(pn->buf, p, n);
+  if (p) memcpy(pn->buf, p, n);
   pn->sz = n;
   pn->n = 0;
   return (pn);
   pn->sz = n;
   pn->n = 0;
   return (pn);
@@ -151,8 +153,7 @@ static int enqueue(pkq *q, pkq_node *pn)
 {
   int rc = 0;
 
 {
   int rc = 0;
 
-  if (!pn)
-    return (0);
+  if (!pn) return (0);
   rc = !q->head;
   pn->next = 0;
   *q->tail = pn;
   rc = !q->head;
   pn->next = 0;
   *q->tail = pn;
@@ -679,9 +680,163 @@ static void shovel(int from, int to)
 static void put(void) { shovel(STDIN_FILENO, make_sock('>')); }
 static void get(void) { shovel(make_sock('<'), STDOUT_FILENO); }
 
 static void put(void) { shovel(STDIN_FILENO, make_sock('>')); }
 static void get(void) { shovel(make_sock('<'), STDOUT_FILENO); }
 
+/*----- Flooding and sinking ----------------------------------------------*/
+
+/* --- Connection jobs --- */
+
+typedef struct conninfo {
+  struct conninfo *next;
+  conn c;
+} conninfo;
+
+#define MAXCONN 32
+static conninfo conns[MAXCONN], *freeconns;
+
+static void (*connhook)(int, conninfo *);
+
+static void connrecycle(conninfo *c) { c->next = freeconns; freeconns = c; }
+
+static void connerr(conninfo *c)
+{
+  if (errno == EAGAIN) connrecycle(c);
+  else die(EXIT_FAILURE, "connect: %s", strerror(errno));
+}
+
+static void connected(int fd, void *p)
+  { if (fd == -1) connerr(p); else connhook(fd, p); }
+
+static void conndoconnect(conninfo *c)
+{
+  int fd;
+  struct sockaddr_un sun;
+  size_t sz;
+
+  if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+    die(EXIT_FAILURE, "socket: %s", strerror(errno));
+  socketaddr(&sun, &sz);
+  if (conn_init(&c->c, &sel, fd, (struct sockaddr *)&sun, sz, connected, c))
+    connerr(c);
+}
+
+static int timerflag;
+
+static void conndrip(struct timeval *tv, void *p)
+{
+  conninfo *c, *cc;
+
+  timerflag = 0;
+  while (freeconns) {
+    c = freeconns; freeconns = 0;
+    while (c) { cc = c->next; conndoconnect(c); c = cc; }
+  }
+}
+
+static void connloop(void (*connected)(int, conninfo *))
+{
+  int i;
+  conninfo **cc;
+  sel_timer t;
+  struct timeval tv;
+
+  connhook = connected;
+  for (i = 0, cc = &freeconns; i < MAXCONN; i++) {
+    *cc = &conns[i];
+    cc = &conns[i].next;
+  }
+  *cc = 0;
+
+  for (;;) {
+    if (freeconns && !timerflag) {
+      gettimeofday(&tv, 0);
+      TV_ADDL(&tv, &tv, 0, 10000);
+      sel_addtimer(&sel, &t, &tv, conndrip, 0);
+      timerflag = 1;
+    }
+    if (sel_select(&sel))
+      die(EXIT_FAILURE, "select: %s", strerror(errno));
+  }
+}
+
+/* --- Sinking (glug glug) --- */
+
+static void sink_close(gobbler *g, int err, void *p)
+{
+  static char baton[4] = "/-\\|";
+  static int pos = 0;
+  static int count = 0;
+  static int state = '?';
+
+  conninfo *c = p;
+
+  if (!err) {
+    if (state == '?')
+      state = isatty(STDOUT_FILENO) ? 'y' : 'n';
+    if (state == 'y') {
+      if (count) count--;
+      else {
+       putchar(baton[pos]);
+       putchar('\b');
+       fflush(stdout);
+       pos++;
+       if (pos >= sizeof(baton)) pos = 0;
+       count = 128;
+      }
+    }
+  }
+  connrecycle(c);
+}
+
+static void sink_connected(int fd, conninfo *c)
+{
+  char dir = '<';
+
+  if (write(fd, &dir, 1) != 1) {
+    moan("write: %s (continuing)", strerror(errno));
+    close(fd);
+    connrecycle(c);
+    return;
+  }
+  make_gobbler(fd, sink_close, c);
+}
+
+static void sink(void) { connloop(sink_connected); }
+
+/* --- Flooding --- */
+
+static void flood_close(dribbler *d, int err, void *p)
+{
+  conninfo *c = p;
+
+  if (err) moan("write: %s (continuing)\n", strerror(errno));
+  destroy_dribbler(d);
+  connrecycle(c);
+}
+
+static void flood_connected(int fd, conninfo *c)
+{
+  static uint32 seq;
+
+  dribbler *d;
+  pkq_node *pn;
+  int i;
+
+#define FLOOD_PKSZ 1024
+
+  pn = make_pkqnode(0, 1 + FLOOD_PKSZ);
+  pn->buf[0] = '>';
+  STORE32(pn->buf + 1, seq);
+  for (i = 4; i < FLOOD_PKSZ; i++)
+    pn->buf[i + 1] = i & 0xff;
+  seq++;
+  d = make_dribbler(fd, flood_close, c);
+  enqueue_dribble(d, pn);
+}
+
+static void flood(void) { connloop(flood_connected); }
+
 /*----- Main code ---------------------------------------------------------*/
 
 /*----- Main code ---------------------------------------------------------*/
 
-static void usage(FILE *fp) { pquis(fp, "Usage: $ [-pg] SOCKET\n"); }
+static void usage(FILE *fp) { pquis(fp, "Usage: $ [-fgps] SOCKET\n"); }
 
 static void version(void)
   { pquis(stdout, "$ (" PACKAGE " version " VERSION")\n"); }
 
 static void version(void)
   { pquis(stdout, "$ (" PACKAGE " version " VERSION")\n"); }
@@ -695,8 +850,10 @@ static void help(void)
 With no options, provides a SLIP interface for TrIPE.\n\
 \n\
 Options:\n\
 With no options, provides a SLIP interface for TrIPE.\n\
 \n\
 Options:\n\
+  -f, --flood  Send packets to TrIPE as fast as possible.\n\
+  -g, --get    Receive packet from TrIPE and write to stdout.\n\
   -p, --put    Send packet on stdin to TrIPE.\n\
   -p, --put    Send packet on stdin to TrIPE.\n\
-  -g, --get    Receive packet from TrIPE and write to stdout.");
+  -s, --sink   Slurp packets out of TrIPE and display progress.");
 }
 
 int main(int argc, char *argv[])
 }
 
 int main(int argc, char *argv[])
@@ -711,15 +868,17 @@ int main(int argc, char *argv[])
       { "version",             0,              0,              'v' },
       { "put",                 0,              0,              'p' },
       { "get",                 0,              0,              'g' },
       { "version",             0,              0,              'v' },
       { "put",                 0,              0,              'p' },
       { "get",                 0,              0,              'g' },
+      { "flood",               0,              0,              'f' },
+      { "sink",                        0,              0,              's' },
       { 0,                     0,              0,              0 }
     };
       { 0,                     0,              0,              0 }
     };
-    i = mdwopt(argc, argv, "hvpg", opt, 0, 0, 0);
+    i = mdwopt(argc, argv, "hvpgfs", opt, 0, 0, 0);
     if (i < 0)
       break;
     switch (i) {
       case 'h': help(); return (0);
       case 'v': version(); return (0);
     if (i < 0)
       break;
     switch (i) {
       case 'h': help(); return (0);
       case 'v': version(); return (0);
-      case 'p': case 'g': mode = i; break;
+      case 'p': case 'g': case 's': case 'f': mode = i; break;
       default: usage(stderr); exit(EXIT_FAILURE); break;
     }
   }
       default: usage(stderr); exit(EXIT_FAILURE); break;
     }
   }
@@ -730,6 +889,8 @@ int main(int argc, char *argv[])
     case 'd': slipif(); break;
     case 'p': put(); break;
     case 'g': get(); break;
     case 'd': slipif(); break;
     case 'p': put(); break;
     case 'g': get(); break;
+    case 'f': flood(); break;
+    case 's': sink(); break;
   }
   return (0);
 }
   }
   return (0);
 }