[cig-commits] r15797 - doc/geodynamics.org/benchmarks/trunk

luis at geodynamics.org luis at geodynamics.org
Sun Oct 11 13:08:55 PDT 2009


Author: luis
Date: 2009-10-11 13:08:54 -0700 (Sun, 11 Oct 2009)
New Revision: 15797

Added:
   doc/geodynamics.org/benchmarks/trunk/watcher.c
Modified:
   doc/geodynamics.org/benchmarks/trunk/.gitignore
   doc/geodynamics.org/benchmarks/trunk/Makefile
   doc/geodynamics.org/benchmarks/trunk/generate.py
Log:
Added watcher executable so we can have automatic html previews

We use the BSD kqueue() API to get notified of any changes to
the source .rst files, polling every 500 milliseconds. Whenever
any of these files change, we simply call system("./generate.py")
to regenerate the corresponding html files as we go.

Modified: doc/geodynamics.org/benchmarks/trunk/.gitignore
===================================================================
--- doc/geodynamics.org/benchmarks/trunk/.gitignore	2009-10-11 20:08:48 UTC (rev 15796)
+++ doc/geodynamics.org/benchmarks/trunk/.gitignore	2009-10-11 20:08:54 UTC (rev 15797)
@@ -1,6 +1,7 @@
 .*.swp
 *.pyc
 /upload.pkl
+/watcher
 
 # for now, ignore these
 /utils

Modified: doc/geodynamics.org/benchmarks/trunk/Makefile
===================================================================
--- doc/geodynamics.org/benchmarks/trunk/Makefile	2009-10-11 20:08:48 UTC (rev 15796)
+++ doc/geodynamics.org/benchmarks/trunk/Makefile	2009-10-11 20:08:54 UTC (rev 15797)
@@ -1,10 +1,14 @@
 
-default: gen
+default: html
 
-gen:
+watcher: watcher.c
+	gcc $< -o $@
+
+html:
+	@echo Scanning for files to regenerate...
 	@./generate.py
 
-up:
+publish:
 	@./upload.py
 
 clean:

Modified: doc/geodynamics.org/benchmarks/trunk/generate.py
===================================================================
--- doc/geodynamics.org/benchmarks/trunk/generate.py	2009-10-11 20:08:48 UTC (rev 15796)
+++ doc/geodynamics.org/benchmarks/trunk/generate.py	2009-10-11 20:08:54 UTC (rev 15797)
@@ -86,7 +86,6 @@
 
     from common import groups, locate
 
-    print "Scanning for files to regenerate..."
     for group in groups:
         for filename in locate('*.html', root=group):
             if needs_update(filename):
@@ -100,7 +99,6 @@
     
     from common import readlines
 
-    print "Scanning for files to regenerate..."
     for filename in readlines('upload.txt'):
         if needs_update(filename):
             regenerate(filename)

Added: doc/geodynamics.org/benchmarks/trunk/watcher.c
===================================================================
--- doc/geodynamics.org/benchmarks/trunk/watcher.c	                        (rev 0)
+++ doc/geodynamics.org/benchmarks/trunk/watcher.c	2009-10-11 20:08:54 UTC (rev 15797)
@@ -0,0 +1,405 @@
+// watcher.c
+//   Watch a set of files using kernel queues (available on BSD systems).
+//   Whenever one of our files changes, we run ./generate.py
+//
+// References:
+//   http://tedunangst.com/kqueue.pdf
+//   http://people.freebsd.org/~jlemon/papers/kqueue.pdf
+//   http://julipedia.blogspot.com/2004/10/example-of-kqueue.html
+//
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <string.h>
+#include <inttypes.h>
+#include <assert.h>
+
+// ----------------------------------------------------------------------------
+
+/* node for our linked list */
+typedef struct l_node {
+    struct l_node *next;    /* pointer to next node */
+    char *path;             /* file path */
+    int fd;                 /* file descriptor */
+    int idx;                /* index into changelist */
+} l_node;
+
+/* single-linked list */
+typedef struct l_list {
+    struct l_node *first;
+    struct l_node *last;
+    int size;
+} l_list;
+
+
+/* allocate new node */
+l_node *l_node_alloc(char *path)
+{
+    l_node *n = (l_node *)malloc(sizeof(l_node));
+    n->next = NULL;
+    n->path = strdup(path);
+    n->fd   = -1;
+    n->idx  = -1;
+    return n;
+}
+
+/* free allocated node */
+void l_node_free(l_node *n)
+{
+    if (!n)
+    {
+        return;
+    }
+
+    if (n->path)
+    {
+        free(n->path);
+        n->path = NULL;
+    }
+
+    if (n->fd != -1)
+    {
+        close(n->fd);
+        n->fd = -1;
+    }
+
+    n->idx = -1;
+
+    free(n);
+    n = NULL;
+}
+
+/* traverse to next node */
+l_node *l_node_next(l_node *n)
+{
+    if (!n)
+    {
+        return n;
+    }
+
+    return n->next;
+}
+
+/* set next node */
+l_node *l_node_set_next(l_node *n, l_node *next)
+{
+    if (!n)
+    {
+        return n;
+    }
+
+    n->next = next;
+
+    return n;
+}
+
+
+/* allocate new list */
+l_list* l_alloc(void)
+{
+    l_list *l = (l_list *)malloc(sizeof(l_list));
+    l->first = NULL;
+    l->last = NULL;
+    return l;
+}
+
+
+/* push an element */
+void l_push(l_list *l, char *path)
+{
+    l_node *n = l_node_alloc(path);
+
+    if (l->first)
+    {
+        l_node_set_next(l->last, n);
+
+        if (l->last)
+        {
+            l->last = n;
+        }
+    }
+    else
+    {
+        l->first = n;
+        l->last = n;
+    }
+
+    l->size++;
+}
+
+/* deallocate list */
+void l_free(l_list *l)
+{
+    if (!l)
+    {
+        return;
+    }
+
+    if (l->first)
+    {
+        l_node *n = l->first;
+        while (n)
+        {
+            l_node *next = n->next;
+            l_node_free(n);
+            n = next;
+        }
+    }
+
+    l->first = NULL;
+    l->last = NULL;
+    l->size = 0;
+
+    free(l);
+    l = NULL;
+}
+
+/* get the source .rst file, given an .html file */
+void getrst(char *filename)
+{
+    /* 
+     * We assume filename is long enough (consists of at least ".html").
+     * Since we're passing in a buffer, we also make the modifications
+     * in-place.
+     */
+    int len = strlen(filename);
+    assert(len > 5);
+    filename[len-1] = '\0';
+    filename[len-2] = '\0';
+    filename[len-3] = 't';
+    filename[len-4] = 's';
+    filename[len-5] = 'r';
+}
+
+/* read lines from a file, and store them in a linked list */
+int readlines(char *filename, l_list *l)
+{
+    FILE *fp;
+    char line[1024];
+
+    fp = fopen(filename, "r");
+
+    if (!fp)
+    {
+        return -1;
+    }
+
+    while (fgets(line, sizeof(line), fp) != NULL)
+    {
+        getrst(line);
+        l_push(l, line);
+    }
+
+    fclose(fp);
+
+    return 0;
+}
+
+/* return a linked list with the filenames we want to monitor */
+l_list *readfiles()
+{
+    l_list *l;
+    l_node *n;
+    int status;
+
+    l = l_alloc();
+
+    status = readlines("upload.txt", l);
+
+    if (status < 0)
+    {
+        l_free(l);
+        return NULL;
+    }
+
+    if (0)
+    {
+        for (n = l->first; n != NULL; n = n->next)
+        {
+            printf("%s\n", n->path);
+        }
+
+        printf("size = %d\n", l->size);
+    }
+
+    return l;
+}
+
+
+// ----------------------------------------------------------------------------
+
+/* A simple routine to return a string for a set of flags. */
+char *flagstring(int flags)
+{
+    static char ret[512];
+    char *or = "";
+
+    ret[0] = '\0'; // clear the string
+
+    if (flags & NOTE_DELETE) { strcat(ret, or); strcat(ret, "NOTE_DELETE"); or = "|"; }
+    if (flags & NOTE_WRITE)  { strcat(ret, or); strcat(ret, "NOTE_WRITE");  or = "|"; }
+    if (flags & NOTE_EXTEND) { strcat(ret, or); strcat(ret, "NOTE_EXTEND"); or = "|"; }
+    if (flags & NOTE_ATTRIB) { strcat(ret, or); strcat(ret, "NOTE_ATTRIB"); or = "|"; }
+    if (flags & NOTE_LINK)   { strcat(ret, or); strcat(ret, "NOTE_LINK");   or = "|"; }
+    if (flags & NOTE_RENAME) { strcat(ret, or); strcat(ret, "NOTE_RENAME"); or = "|"; }
+    if (flags & NOTE_REVOKE) { strcat(ret, or); strcat(ret, "NOTE_REVOKE"); or = "|"; }
+
+    return ret;
+}
+
+int errorflag(struct kevent *eventlist, int nevents)
+{
+    int i;
+    for (i = 0; i < nevents; i++)
+    {
+        if (eventlist[i].flags == EV_ERROR)
+        {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+// ----------------------------------------------------------------------------
+int main(int argc, char *argv[])
+{
+    /* some loop variables */
+    int i;
+    l_node *n;
+
+    /* list of files */
+    l_list *f;
+    int num_files;
+    int num_event_fds;
+    int num_event_slots;
+
+    /* kqueue api */
+    int kq;
+    struct kevent *changelist;
+    struct kevent *eventlist;
+    struct timespec timeout;
+    unsigned int vnode_events;
+
+    /* get list of files to monitor */
+    f = readfiles();
+    if (f == NULL)
+    {
+        fprintf(stderr, "Could not read list of files.");
+        exit(-1);
+    }
+    
+    num_files = f->size;
+    num_event_fds = num_files;
+    num_event_slots = 1;
+
+    changelist = (struct kevent *)malloc(num_event_fds * sizeof(struct kevent));
+    eventlist = (struct kevent *)malloc(num_event_slots * sizeof(struct kevent));
+
+    for (n = f->first; n != NULL; n = n->next)
+    {
+        n->fd = open(n->path, O_EVTONLY);
+        if (n->fd <= 0)
+        {
+            fprintf(stderr, "The file %s could not be opened for monitoring. ", n->path);
+            fprintf(stderr, "Error was: %s.\n", strerror(errno));
+            exit(-1);
+        }
+    }
+
+    /* open a kernel queue */
+    kq = kqueue();
+    if (kq < 0)
+    {
+        fprintf(stderr, "Could not open kernel queue. ");
+        fprintf(stderr, "Error was: %s.\n", strerror(errno));
+        exit(-1);
+    }
+
+    /* setup a list of events to monitor */
+    vnode_events = NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND | NOTE_RENAME;
+    for (i = 0, n = f->first; n != NULL; i++, n = n->next)
+    {
+        n->idx = i;
+        EV_SET(&changelist[i], n->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, vnode_events, 0, n);
+    }
+
+    /* set the timeout to wake us every half second */
+    timeout.tv_sec = 0;             // 0 seconds
+    timeout.tv_nsec = 500000000;    // 500 milliseconds
+
+    /* handle events */
+    printf("Watching %d files...\n", num_files);
+    while (1)
+    {
+        int event_count = kevent(kq, changelist, num_event_fds, eventlist, num_event_slots, &timeout);
+
+        if ((event_count < 0) || errorflag(eventlist, num_event_slots))
+        {
+            fprintf(stderr, "An error occurred (event count %d). ", event_count);
+            fprintf(stderr, "The error was: %s.\n", strerror(errno));
+            break;
+        }
+
+        if (event_count)
+        {
+            u_int fflags = eventlist[0].fflags;
+            l_node *n = (l_node *)eventlist[0].udata;
+
+            /* 
+             * Reopen file descriptor if file is deleted.
+             *
+             * We do this because when vim saves a file, it replaces it
+             * by the temporary copy it used for editing. This means our
+             * original file descriptor is stale, since original file gets
+             * renamed and then deleted. However, the file was only replaced,
+             * so we can open it again using the same path.
+             */
+            if (fflags & NOTE_DELETE)
+            {
+                /* save the old file descriptor */
+                int fd_old = n->fd;
+
+                /* reopen with same path */
+                n->fd = open(n->path, O_EVTONLY);
+
+                /* quit if the file was actually deleted */
+                if (n->fd == -1)
+                {
+                    fprintf(stderr, "File %s was deleted!\n", n->path);
+                    break;
+                }
+
+                /* start watching the new file descriptor */
+                EV_SET(&changelist[n->idx], n->fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, vnode_events, 0, n);
+
+                /* no need to issue EV_DELETE on fd_old. calling close() suffices. */
+                close(fd_old);
+            }
+
+            /* run "./generate.py" whenever any changes are detected */
+            if ((fflags & NOTE_EXTEND) ||
+                (fflags & NOTE_WRITE) ||
+                (fflags & NOTE_DELETE))
+            {
+                system("./generate.py");
+            }
+        }
+
+        /* reset the timeout (might change after a signal interrupt) */
+        timeout.tv_sec = 0;
+        timeout.tv_nsec = 500000000;
+    }
+
+    /* clean up */
+    free(changelist);
+    free(eventlist);
+    l_free(f);
+
+    return 0;
+}



More information about the CIG-COMMITS mailing list