diff -ru xen-unstable-10712/tools/console/daemon/io.c xen-unstable-10712-new/tools/console/daemon/io.c --- xen-unstable-10712/tools/console/daemon/io.c 2006-07-21 13:31:22.000000000 -0400 +++ xen-unstable-10712-new/tools/console/daemon/io.c 2006-08-29 11:35:06.000000000 -0400 @@ -37,6 +37,7 @@ #include #include #include +#include #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) @@ -44,6 +45,13 @@ /* Each 10 bits takes ~ 3 digits, plus one, plus one for nul terminator. */ #define MAX_STRLEN(x) ((sizeof(x) * CHAR_BIT + CHAR_BIT-1) / 10 * 3 + 2) +/* How much data to allow in a measurement period - bytes */ +#define RATE_LIMIT_AMOUNT (5 * 1024) +/* Duration of the measurement period in - ms */ +#define RATE_LIMIT_PERIOD 200 +/* Delay before allowing a rate limited domain to continue - ms */ +#define RATE_LIMIT_DELAY 200 + struct buffer { char *data; @@ -51,6 +59,10 @@ size_t size; size_t capacity; size_t max_capacity; + size_t cumulative; + long long checkpoint; + int rate_limited; + evtchn_port_t limited_port; }; struct domain @@ -68,12 +80,28 @@ }; static struct domain *dom_head; +static long long int now; + +static int need_ratelimit(struct domain *dom, size_t consumed) +{ + dom->buffer.cumulative += consumed; + /* Rate limit if we consumed N bytes in < M milliseconds */ + if (dom->buffer.cumulative > RATE_LIMIT_AMOUNT) { + dom->buffer.cumulative = 0; + dom->buffer.rate_limited = (now - dom->buffer.checkpoint) < RATE_LIMIT_PERIOD ? 1 : 0; + dom->buffer.checkpoint = now; + + return dom->buffer.rate_limited; + } + return 0; +} static void buffer_append(struct domain *dom) { struct buffer *buffer = &dom->buffer; XENCONS_RING_IDX cons, prod, size; struct xencons_interface *intf = dom->interface; + size_t start = buffer->size; cons = intf->out_cons; prod = intf->out_prod; @@ -98,7 +126,8 @@ mb(); intf->out_cons = cons; - xc_evtchn_notify(dom->xce_handle, dom->local_port); + if (!need_ratelimit(dom, buffer->size - start)) + xc_evtchn_notify(dom->xce_handle, dom->local_port); if (buffer->max_capacity && buffer->size > buffer->max_capacity) { @@ -514,7 +543,10 @@ buffer_append(dom); - (void)xc_evtchn_unmask(dom->xce_handle, port); + if (!dom->buffer.rate_limited) + (void)xc_evtchn_unmask(dom->xce_handle, port); + else + dom->buffer.limited_port = port; } static void handle_xs(void) @@ -545,10 +577,17 @@ { fd_set readfds, writefds; int ret; + struct timeval tv; + + if (gettimeofday(&tv, NULL) < 0) + return; + now = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); do { struct domain *d, *n; int max_fd = -1; + long long future = 0; + struct timeval timeout; FD_ZERO(&readfds); FD_ZERO(&writefds); @@ -557,7 +596,19 @@ max_fd = MAX(xs_fileno(xs), max_fd); for (d = dom_head; d; d = d->next) { - if (d->xce_handle != -1) { + if (d->buffer.rate_limited) { + long long ourfuture = d->buffer.checkpoint + RATE_LIMIT_DELAY; + /* Shouldn't happen, but sanity check at least 1 ms, otherwise + we could get stuck in select() without a timeout, and rate + limited */ + if (ourfuture <= now) + ourfuture = now + 1; + + /* If our timeout is sooner than any other domain's timeout */ + if (!future || + ourfuture < future) + future = ourfuture; + } else if (d->xce_handle != -1) { int evtchn_fd = xc_evtchn_fd(d->xce_handle); FD_SET(evtchn_fd, &readfds); max_fd = MAX(evtchn_fd, max_fd); @@ -573,16 +624,50 @@ } } - ret = select(max_fd + 1, &readfds, &writefds, 0, NULL); + /* If any domain has been rate limited, we need to work + out what timeout to supply to select */ + if (future) { + long long duration = (future - now); + timeout.tv_sec = duration / 1000; + timeout.tv_usec = (duration - (timeout.tv_sec * 1000)) * 1000; + } + + if ((ret = select(max_fd + 1, &readfds, &writefds, 0, future ? &timeout : NULL)) < 0) + if (errno != EINTR) + break; + + /* Update the global timestamp - not strictly neccessary, + but by keeping a cached timestamp we avoid having to + call gettimeofday (3 * num(domains)) on each iteration + which is a worthwhile optimization tradeoff against a little + inaccurracy - it assumes data processing time is negligable*/ + if (gettimeofday(&tv, NULL) < 0) + return; + now = (tv.tv_sec * 1000) + (tv.tv_usec / 1000); if (FD_ISSET(xs_fileno(xs), &readfds)) handle_xs(); for (d = dom_head; d; d = n) { n = d->next; - if (d->xce_handle != -1 && - FD_ISSET(xc_evtchn_fd(d->xce_handle), &readfds)) - handle_ring_read(d); + /* If the domain's rate limited, we need to see if its time to + unblock it, rather than checking the evt channel */ + if (d->buffer.rate_limited) { + /* We asked to wait for N ms, but if we're within 5 ms + of that unblock anyway, because select() often returns + a couple of ms earlier than the requested timeout. This + avoids wastefully select()'ing again with a 1 ms timeout */; + if ((now - d->buffer.checkpoint) > (200-5)) { + d->buffer.rate_limited = 0; + d->buffer.checkpoint = now; + xc_evtchn_notify(d->xce_handle, d->local_port); + (void)xc_evtchn_unmask(d->xce_handle, d->buffer.limited_port); + } + } else { + if (d->xce_handle != -1 && + FD_ISSET(xc_evtchn_fd(d->xce_handle), &readfds)) + handle_ring_read(d); + } if (d->tty_fd != -1) { if (FD_ISSET(d->tty_fd, &readfds))