Eliminate excessive CPU consumption when redirecting UART over SSH
Redirecting the external UART via SSH caused the console-server,
console-client, and dropbear to consume ~30% of the available CPU each
when a large amount of data was being written to the UART output.
Buffering all of the small 16550 FIFO bytes into a larger packet and
then sending that to the SSH SW allows more efficient transmission
over the ethernet connection.
Tested this by "ssh root@<bmc.ip.addr> -p 2200" on a system running a
CentOS distribution. Using a BASH console run a large binary file
through "od -t x1 <fname>" to create a large amount of traffic. At
the BMC console run "top" to review the CPU usage. My experience is
after this change is applied:
console-server: ~25% CPU
dropbear: ~3% CPU
console-client: ~1% CPU
Change-Id: Ibabfd285e97a487e7ff040e1cb3159fbff360328
Signed-off-by: Johnathan Mantey <johnathanx.mantey@intel.com>
diff --git a/console-server.c b/console-server.c
index cde4bfc..d814dff 100644
--- a/console-server.c
+++ b/console-server.c
@@ -31,9 +31,11 @@
#include <string.h>
#include <getopt.h>
#include <limits.h>
+#include <time.h>
#include <termios.h>
#include <sys/types.h>
+#include <sys/time.h>
#include <poll.h>
#include "console-server.h"
@@ -66,7 +68,9 @@
struct poller {
struct handler *handler;
void *data;
- poller_fn_t fn;
+ poller_event_fn_t event_fn;
+ poller_timeout_fn_t timeout_fn;
+ struct timeval timeout;
bool remove;
};
@@ -456,6 +460,26 @@
}
}
+static int get_current_time(struct timeval *tv)
+{
+ struct timespec t;
+ int rc;
+
+ /*
+ * We use clock_gettime(CLOCK_MONOTONIC) so we're immune to
+ * local time changes. However, a struct timeval is more
+ * convenient for calculations, so convert to that.
+ */
+ rc = clock_gettime(CLOCK_MONOTONIC, &t);
+ if (rc)
+ return rc;
+
+ tv->tv_sec = t.tv_sec;
+ tv->tv_usec = t.tv_nsec / 1000;
+
+ return 0;
+}
+
struct ringbuffer_consumer *console_ringbuffer_consumer_register(
struct console *console,
ringbuffer_poll_fn_t poll_fn, void *data)
@@ -464,8 +488,9 @@
}
struct poller *console_poller_register(struct console *console,
- struct handler *handler, poller_fn_t poller_fn,
- int fd, int events, void *data)
+ struct handler *handler, poller_event_fn_t poller_fn,
+ poller_timeout_fn_t timeout_fn, int fd,
+ int events, void *data)
{
struct poller *poller;
int n;
@@ -473,7 +498,8 @@
poller = malloc(sizeof(*poller));
poller->remove = false;
poller->handler = handler;
- poller->fn = poller_fn;
+ poller->event_fn = poller_fn;
+ poller->timeout_fn = timeout_fn;
poller->data = data;
/* add one to our pollers array */
@@ -547,7 +573,57 @@
console->pollfds[i].events = events;
}
-static int call_pollers(struct console *console)
+void console_poller_set_timeout(struct console *console, struct poller *poller,
+ const struct timeval *tv)
+{
+ struct timeval now;
+ int rc;
+
+ rc = get_current_time(&now);
+ if (rc)
+ return;
+
+ timeradd(&now, tv, &poller->timeout);
+}
+
+static int get_poll_timeout(struct console *console, struct timeval *cur_time)
+{
+ struct timeval *earliest, interval;
+ struct poller *poller;
+ int i;
+
+ earliest = NULL;
+
+ for (i = 0; i < console->n_pollers; i++) {
+ poller = console->pollers[i];
+
+ if (poller->timeout_fn && timerisset(&poller->timeout) &&
+ (!earliest ||
+ (earliest && timercmp(&poller->timeout, earliest, <)))){
+ // poller is buffering data and needs the poll
+ // function to timeout.
+ earliest = &poller->timeout;
+ }
+ }
+
+ if (earliest) {
+ if (timercmp(earliest, cur_time, >)) {
+ /* recalculate the timeout period, time period has
+ * not elapsed */
+ timersub(earliest, cur_time, &interval);
+ return ((interval.tv_sec * 1000) +
+ (interval.tv_usec / 1000));
+ } else {
+ /* return from poll immediately */
+ return 0;
+ }
+ } else {
+ /* poll indefinitely */
+ return -1;
+ }
+}
+
+static int call_pollers(struct console *console, struct timeval *cur_time)
{
struct poller *poller;
struct pollfd *pollfd;
@@ -563,16 +639,33 @@
for (i = 0; i < console->n_pollers; i++) {
poller = console->pollers[i];
pollfd = &console->pollfds[i];
+ prc = POLLER_OK;
- if (!pollfd->revents)
- continue;
+ /* process pending events... */
+ if (pollfd->revents) {
+ prc = poller->event_fn(poller->handler, pollfd->revents,
+ poller->data);
+ if (prc == POLLER_EXIT)
+ rc = -1;
+ else if (prc == POLLER_REMOVE)
+ poller->remove = true;
+ }
- prc = poller->fn(poller->handler, pollfd->revents,
- poller->data);
- if (prc == POLLER_EXIT)
- rc = -1;
- else if (prc == POLLER_REMOVE)
- poller->remove = true;
+ if ((prc == POLLER_OK) && poller->timeout_fn &&
+ timerisset(&poller->timeout) &&
+ timercmp(&poller->timeout, cur_time, <=)) {
+ /* One of the ringbuffer consumers is buffering the
+ data stream. The amount of idle time the consumer
+ desired has expired. Process the buffered data for
+ transmission. */
+ timerclear(&poller->timeout);
+ prc = poller->timeout_fn(poller->handler, poller->data);
+ if (prc == POLLER_EXIT) {
+ rc = -1;
+ } else if (prc == POLLER_REMOVE) {
+ poller->remove = true;
+ }
+ }
}
/**
@@ -606,7 +699,8 @@
int run_console(struct console *console)
{
sighandler_t sighandler_save;
- int rc;
+ struct timeval tv;
+ int rc, timeout;
sighandler_save = signal(SIGINT, sighandler);
@@ -622,8 +716,18 @@
break;
}
+ rc = get_current_time(&tv);
+ if (rc) {
+ warn("Failed to read current time");
+ break;
+ }
+
+ timeout = get_poll_timeout(console, &tv);
+
rc = poll(console->pollfds,
- console->n_pollers + MAX_INTERNAL_POLLFD, -1);
+ console->n_pollers + MAX_INTERNAL_POLLFD,
+ timeout);
+
if (rc < 0) {
if (errno == EINTR) {
continue;
@@ -651,7 +755,7 @@
}
/* ... and then the pollers */
- rc = call_pollers(console);
+ rc = call_pollers(console, &tv);
if (rc)
break;
}