]> sourceware.org Git - systemtap.git/commitdiff
monitor mode: staprun revamp
authorFrank Ch. Eigler <fche@redhat.com>
Wed, 23 Mar 2016 14:48:28 +0000 (10:48 -0400)
committerFrank Ch. Eigler <fche@redhat.com>
Wed, 23 Mar 2016 15:01:13 +0000 (11:01 -0400)
A group of changes for usability & stability of the staprun side of
--monitor mode.  The gist of it is to let staprun persist awhile after
a script exits (whether interactively commanded with 'q', or with the
script exiting due to an exit/error), so that its outputs can be seen.

* monitor.c (monitor_state): Add new "exited" and "exited_help" states.
  (monitor_render): Remove code that reads input from module.
  Support exited_help subset text.
  (monitor_remember_output_line): New utility function to strndup
  module output lines into our circular buffer.
  (monitor_input): Revamp logic to use read(2) syscalls instead of
  temporary fdopen() FILEs to read from module-output pipe, for
  several reasons.
  (monitor_exited): New function to enter exited state.
* relay.c (reader_thread): Use write(2) loop when feeding output
  pipe/file, in case it gets full.
  (init_relayfs): Drop F_SETPIPE_SZ; it is not needed with write loop
  here and proper nonblocking read on other side.
* mainloop.c (signal_thread): Keep it running after each signal, just
  in case a ^C is repeated.  Encode SIGQUIT with a set to 'load_only'
  instead of pending_interrupts count.
  (stp_main_loop): Treat several startup-time errors as worthy of
  stap module removal/cleanup rather than detaching.  Within main
  loop proper, call cleanup_and_exit upon pending_interrupts, as
  the single standard spot where a stap module shutdown from any
  cause should come.  Redirect warning/error messages to monitor
  output buffer.  Notify monitor code of received STP_EXIT message.
* staprun.h: Provide inline-void stub functions for the !MONITOR_LIBS
  case.

staprun/mainloop.c
staprun/monitor.c
staprun/relay.c
staprun/staprun.h

index 9d2458f05ef5c8063d55d9f94398a0d39dbcb231..9df06c506b0458062b19c2ccee892f8676a1fa67 100644 (file)
 #include <sys/select.h>
 #include <search.h>
 #include <wordexp.h>
-#if HAVE_MONITOR_LIBS
-#include <json-c/json.h>
-#include <curses.h>
-#endif
 
 
 #define WORKAROUND_BZ467568 1  /* PR 6964; XXX: autoconf when able */
@@ -47,11 +43,10 @@ static void *signal_thread(void *arg)
     }
     dbug(2, "sigproc %d (%s)\n", signum, strsignal(signum));
     if (signum == SIGQUIT) {
-      pending_interrupts += 2;
-      break;
+      load_only = 1; /* flag for stp_main_loop */
+      pending_interrupts ++;
     } else if (signum == SIGINT || signum == SIGHUP || signum == SIGTERM) {
       pending_interrupts ++;
-      break;
     }
   }
   /* Notify main thread (interrupts select). */
@@ -140,13 +135,11 @@ static void setup_main_signals(void)
   sa.sa_handler = chld_proc;
   sigaction(SIGCHLD, &sa, NULL);
 
-#if HAVE_MONITOR_LIBS
   if (monitor)
     {
       sa.sa_handler = monitor_winch;
       sigaction(SIGWINCH, &sa, NULL);
     }
-#endif
 
   /* This signal handler is notified from the signal_thread
      whenever a interruptable event is detected. It will
@@ -498,10 +491,8 @@ void cleanup_and_exit(int detach, int rc)
   int rstatus;
   struct sigaction sa;
 
-#if HAVE_MONITOR_LIBS
   if (monitor)
     monitor_cleanup();
-#endif
 
   if (exiting)
     return;
@@ -620,9 +611,7 @@ int stp_main_loop(void)
   int rc;
   int maxfd;
   struct timeval tv;
-#if HAVE_MONITOR_LIBS
   struct timespec ts;
-#endif
   struct timespec *timeout = NULL;
   fd_set fds;
   sigset_t blockset, mainset;
@@ -635,7 +624,7 @@ int stp_main_loop(void)
   rc = send_request(STP_READY, NULL, 0);
   if (rc != 0) {
     perror ("Unable to send STP_READY");
-    cleanup_and_exit (1, rc);
+    cleanup_and_exit(0, rc);
   }
 
   flags = fcntl(control_channel, F_GETFL);
@@ -660,7 +649,6 @@ int stp_main_loop(void)
     pthread_sigmask(SIG_BLOCK, &blockset, &mainset);
   }
 
-#if HAVE_MONITOR_LIBS
   /* In monitor mode, we must timeout pselect to poll the monitor
    * interface. */
   if (monitor)
@@ -670,28 +658,22 @@ int stp_main_loop(void)
       ts.tv_nsec = 500*1000*1000;
       timeout = &ts;
     }
-#endif
 
   /* handle messages from control channel */
   while (1) {
-#if HAVE_MONITOR_LIBS
     if (monitor)
       {
         monitor_input();
         monitor_render();
       }
-#endif
 
     if (pending_interrupts) {
          int btype = STP_EXIT;
          int rc = write(control_channel, &btype, sizeof(btype));
          dbug(2, "signal-triggered %d exit rc %d\n", pending_interrupts, rc);
-         if (pending_interrupts >= 2) {
-            cleanup_and_exit (1, 0);
-         }
+         cleanup_and_exit (load_only /* = detach */, 0);
     }
 
-
     /* If the runtime does not implement select() on the command
        filehandle, we have to poll periodically.  The polling interval can
        be relatively large, since we don't receive EAGAIN during the
@@ -717,14 +699,12 @@ int stp_main_loop(void)
        FD_ZERO(&fds);
        FD_SET(control_channel, &fds);
         maxfd = control_channel;
-#if HAVE_MONITOR_LIBS
        if (monitor) {
          FD_SET(STDIN_FILENO, &fds);
          FD_SET(monitor_pfd[0], &fds);
          if (monitor_pfd[0] > maxfd)
            maxfd = monitor_pfd[0];
        }
-#endif
        res = pselect(maxfd + 1, &fds, NULL, NULL, timeout, &mainset);
        if (res < 0 && errno != EINTR)
          {
@@ -753,8 +733,11 @@ int stp_main_loop(void)
       if (strncmp(recvbuf.payload.data, "WARNING: ", 9) == 0) {
               if (suppress_warnings) break;
               if (verbose) { /* don't eliminate duplicates */
-                      /* trim "WARNING: " */
-                      warn("%.*s", (int) nb-9, recvbuf.payload.data+9);
+                      if (monitor)
+                              monitor_remember_output_line (recvbuf.payload.data, nb);
+                      else
+                              /* trim "WARNING: " */
+                              warn("%.*s", (int) nb-9, recvbuf.payload.data+9);
                       break;
               } else { /* eliminate duplicates */
                       static void *seen = 0;
@@ -764,15 +747,21 @@ int stp_main_loop(void)
 
                       if (! dupstr) {
                               /* OOM, should not happen. */
-                              /* trim "WARNING: " */
-                              warn("%.*s", (int) nb-9, recvbuf.payload.data+9);
+                              if (monitor)
+                                      monitor_remember_output_line (recvbuf.payload.data, nb);
+                              else
+                                      /* trim "WARNING: " */
+                                      warn("%.*s", (int) nb-9, recvbuf.payload.data+9);
                               break;
                       }
 
                       retval = tfind (dupstr, & seen, (int (*)(const void*, const void*))strcmp);
                       if (! retval) { /* new message */
-                              /* trim "WARNING: " */
-                              warn("%.*s", strlen(dupstr)-9, dupstr+9);
+                              if (monitor)
+                                      monitor_remember_output_line (recvbuf.payload.data, nb);
+                              else
+                                      /* trim "WARNING: " */
+                                      warn("%.*s", strlen(dupstr)-9, dupstr+9);
 
                               /* We set a maximum for stored warning messages,
                                  to prevent a misbehaving script/environment
@@ -807,18 +796,28 @@ int stp_main_loop(void)
       /* Note that "ERROR:" should not be translated, since it is
        * part of the module cmd protocol. */
       } else if (strncmp(recvbuf.payload.data, "ERROR: ", 7) == 0) {
-              /* trim "ERROR: " */
-              err("%.*s", (int) nb-7, recvbuf.payload.data+7);
+              if (monitor)
+                      monitor_remember_output_line (recvbuf.payload.data, nb);
+              else
+                      /* trim "ERROR: " */
+                      err("%.*s", (int) nb-7, recvbuf.payload.data+7);
               error_detected = 1;
       } else { /* neither warning nor error */
-              eprintf("%.*s", (int) nb, recvbuf.payload.data);
+              if (monitor)
+                      monitor_remember_output_line (recvbuf.payload.data, nb);
+              else
+                      eprintf("%.*s", (int) nb, recvbuf.payload.data);
       }
       break;
     case STP_EXIT:
       {
         /* module asks us to unload it and exit */
         dbug(2, "got STP_EXIT\n");
-        cleanup_and_exit(0, error_detected);
+        if (monitor)
+                monitor_exited();
+        else
+                cleanup_and_exit(0, error_detected);
+        /* monitor mode exit handled elsewhere, later. */
         break;
       }
     case STP_REQUEST_EXIT:
@@ -896,7 +895,7 @@ int stp_main_loop(void)
         rc = send_request(STP_START, &ts, sizeof(ts));
        if (rc != 0) {
          perror ("Unable to send STP_START");
-         cleanup_and_exit (1, rc);
+         cleanup_and_exit(0, rc);
        }
         if (load_only)
           cleanup_and_exit(1, 0);
index b0921fe2a4235d0f979d9d58babd77e2e9e0ca01..35d3269bfa0bc6fdb637fc77205194d690eb3fec 100644 (file)
@@ -8,6 +8,7 @@
 #define MAX_COLS 256
 #define MAX_DATA 8192
 #define MAX_HISTORY 8192
+#define MAX_LINELENGTH 4096
 
 #define MIN(X,Y) (((X) < (Y)) ? (X) : (Y))
 #define MAX(X,Y) (((X) > (Y)) ? (X) : (Y))
 
 typedef struct History_Queue
 {
-  char *buf[MAX_HISTORY];
-  size_t allocated[MAX_HISTORY];
-  int front;
-  int back;
+  char *lines[MAX_HISTORY]; /* each malloc/strdup'd(). */
+  char linebuf[MAX_LINELENGTH]; /* beyond this length, we cut up lines */
+  int linebuf_ptr; /* index into linebuf[] where next line piece should be read */
+  int oldest;
+  int newest;
   int count;
 } History_Queue;
 
@@ -38,8 +40,10 @@ enum probe_attributes
 static enum state
 {
   normal,
+  exited,
   insert,
-  help
+  help,
+  exited_help,
 } monitor_state; 
 
 static History_Queue h_queue;
@@ -52,7 +56,7 @@ static int output_scroll = 0;
 static time_t elapsed_time = 0;
 static time_t start_time = 0;
 static int resized = 0;
-static int input = 0;
+static int input = 0; /* fresh input received in monitor_input() */
 static int rendered = 0;
 
 /* Forward declarations */
@@ -212,38 +216,19 @@ void monitor_cleanup(void)
 void monitor_render(void)
 {
   FILE *monitor_fp;
-  FILE *output_fp;
   char path[PATH_MAX];
   time_t current_time = time(NULL);
   int monitor_x, monitor_y, max_cols, max_rows, cur_y, cur_x;
-
+  
   if (resized)
     handle_resize();
 
-  output_fp = fdopen(monitor_pfd[0], "r");
-
-  /* Render normal systemtap output */
-  if (output_fp && monitor_set)
-    {
-      int i;
-      int bytes;
-
-      while ((bytes = getline(&h_queue.buf[h_queue.back],
-              &h_queue.allocated[h_queue.back], output_fp)) != -1)
-        {
-          h_queue.back = (h_queue.back+1) % MAX_HISTORY;
-          if (h_queue.count < MAX_HISTORY)
-            h_queue.count++;
-          else
-            h_queue.front = (h_queue.front+1) % MAX_HISTORY;
-        }
-
-      wclear(monitor_output);
-      for (i = 0; i < h_queue.count-output_scroll; i++)
-        wprintw(monitor_output, "%s", h_queue.buf[(h_queue.front+i) % MAX_HISTORY]);
-
-      wrefresh(monitor_output);
-    }
+  /* Render previously recorded output */
+  wclear(monitor_output);
+  getmaxyx(monitor_output, monitor_y, monitor_x);
+  for (int i = 0; i < MIN(monitor_y, h_queue.count-output_scroll); i++)
+    wprintw(monitor_output, "%s", h_queue.lines[(h_queue.oldest+i) % MAX_HISTORY]);
+  wrefresh(monitor_output);
 
   if (!input && rendered && (elapsed_time = current_time - start_time) < monitor_interval)
     return;
@@ -274,7 +259,23 @@ void monitor_render(void)
       mvwprintw(monitor_status, max_rows-1, 0, "press h to go back\n");
       wrefresh(monitor_status);
     }
-  else
+  if (monitor_state == exited_help)
+    {
+      /* Render help page */
+      rendered = 0;
+      wclear(monitor_status);
+      wprintw(monitor_status, "EXITED MONITOR MODE COMMANDS\n");
+      wprintw(monitor_status, "h - Display help page.\n");
+      wprintw(monitor_status, "s - Rotate sort columns for probes.\n");
+      wprintw(monitor_status, "q - Quit script.\n");
+      wprintw(monitor_status, "j/DownArrow - Scroll down the probe list.\n");
+      wprintw(monitor_status, "k/UpArrow - Scroll up the probe list.\n");
+      wprintw(monitor_status, "d/PageDown - Scroll down the output by one page.\n");
+      wprintw(monitor_status, "u/PageUp - Scroll up the probe list by one page.\n");
+      mvwprintw(monitor_status, max_rows-1, 0, "press h to go back\n");
+      wrefresh(monitor_status);
+    }
+  else if (monitor_state == normal)
     {
       /* Render monitor mode statistics */
       if (sprintf_chk(path, "/proc/systemtap/%s/monitor_status", modname))
@@ -318,7 +319,7 @@ void monitor_render(void)
 
           if (monitor_state == insert)
             mvwprintw(monitor_status, max_rows-1, 0, "enter probe index: %s\n", probe);
-          else
+          else if (monitor_state == normal)
             mvwprintw(monitor_status, max_rows-1, 0,
                       "press h for help\n");
           wmove(monitor_status, 0, 0);
@@ -427,17 +428,90 @@ void monitor_render(void)
           json_object_put(jso);
         }
     }
+  else /* exited? */
+    {
+      mvwprintw(monitor_status, max_rows-1, 0,
+                "EXITED: press h for help, q to quit\n");
+      wrefresh(monitor_status);
+    }
 }
 
+
+
+void monitor_remember_output_line(const char* buf, const size_t bytes)
+{
+  free (h_queue.lines[h_queue.newest]);
+  h_queue.lines[h_queue.newest] = strndup(buf, bytes);
+  h_queue.newest = (h_queue.newest+1) % MAX_HISTORY;
+
+  if (h_queue.count < MAX_HISTORY)
+    h_queue.count++; /* and h_queue.oldest stays at 0 */
+  else
+    h_queue.oldest = (h_queue.oldest+1) % MAX_HISTORY;
+}
+  
+
+
+void monitor_exited(void)
+{
+  monitor_state = exited;
+  input = 1;
+}
+
+
 void monitor_input(void)
 {
   static int i = 0;
   int ch;
   int max_rows, max_cols;
 
+  /* NB: monitor_pfd[0] is the read side, O_NONBLOCK, of the pipe
+     that collects/serializes all the per-cpu outputs.  We can't
+     use stdio calls. */
+  
+  /* Collect normal systemtap output */
+  while (monitor_set)
+    {
+      ssize_t bytes = read(monitor_pfd[0],
+                           h_queue.linebuf + h_queue.linebuf_ptr,
+                           MAX_LINELENGTH - h_queue.linebuf_ptr);
+      if (bytes <= 0)
+        break;
+
+      /* Start scanning the linebuf[] for lines - \n.
+         Plop each one found into the h_queue.lines[] ring. */
+      char *p = h_queue.linebuf; /* scan position */
+      char *p_end = h_queue.linebuf + h_queue.linebuf_ptr + bytes; /* one past last byte */
+      char *line = p;
+      while (p <= p_end)
+        {
+          if (*p == '\n') /* got a line */
+            {
+              monitor_remember_output_line(line, (p-line)+1); /* strlen, including \n */
+              line = p+1;
+            }
+          p ++;
+        }
+
+      if (line != p)
+        {
+          /* Move trailing partial line (if any) to front of buffer. */
+          memmove (h_queue.linebuf, line, (p_end - line));
+          h_queue.linebuf_ptr = (p_end - line);
+        }
+      else
+        {
+          /* No line found in entire buffer!  Pretend it was all one line. */
+          monitor_remember_output_line(line, (p_end - line));
+          h_queue.linebuf_ptr = 0;
+        }
+    }          
+
+
   switch (monitor_state)
     {
-      case normal:
+    case normal:
+    case exited:
         ch = getch();
         switch (ch)
           {
@@ -479,13 +553,16 @@ void monitor_input(void)
               write_command("pause", 5);
               break;
             case 'q':
-              write_command("quit", 4);
+              if (monitor_state == exited)
+                cleanup_and_exit(0, 0 /* error_detected unavailable here */ );
+              else
+                write_command("quit", 4);
               break;
             case 't':
               monitor_state = insert;
               break;
             case 'h':
-              monitor_state = help;
+              monitor_state = (monitor_state == exited) ? exited_help : help;
               break;
           }
         if (ch != ERR)
@@ -514,9 +591,10 @@ void monitor_input(void)
           }
         break;
       case help:
+      case exited_help:
         ch = getch();
         if(ch == 'h')
-          monitor_state = normal;
+          monitor_state = (monitor_state == exited_help) ? exited : normal;
         break;
     }
 }
index 11225ac20efea30ff66999a1ca8e843f9584f061..06576f7931ff81b755ae1e2db7b12ef13ae53506 100644 (file)
@@ -192,6 +192,9 @@ static void *reader_thread(void *data)
                 }
 
                while ((rc = read(relay_fd[cpu], buf, sizeof(buf))) > 0) {
+                        int wbytes = rc;
+                        char *wbuf = buf;
+                        
                        /* Switching file */
                        pthread_mutex_lock(&mutex[cpu]);
                        if ((fsize_max && ((wsize + rc) > fsize_max)) ||
@@ -206,15 +209,19 @@ static void *reader_thread(void *data)
                        }
                        pthread_mutex_unlock(&mutex[cpu]);
 
-                       /* Prevent pipe overflow after closing */
-                       if (monitor && monitor_end)
-                               return 0;
-                       if (write(out_fd[cpu], buf, rc) != rc) {
-                               if (errno != EPIPE)
-                                       perr("Couldn't write to output %d for cpu %d, exiting.", out_fd[cpu], cpu);
-                               goto error_out;
-                       }
-                       wsize += rc;
+                        /* Copy loop.  Must repeat write(2) in case of a pipe overflow
+                           or other transient fullness. */
+                        while (wbytes > 0) {
+                                rc = write(out_fd[cpu], wbuf, wbytes);
+                                if (rc <= 0) {
+                                       perr("Couldn't write to output %d for cpu %d, exiting.",
+                                             out_fd[cpu], cpu);
+                                        goto error_out;
+                                }
+                                wbytes -= rc;
+                                wbuf += rc;
+                        }
+                       wsize += wbytes;
                }
         } while (!stop_threads);
        dbug(3, "exiting thread for cpu %d\n", cpu);
@@ -390,10 +397,13 @@ int init_relayfs(void)
                                        perr("Couldn't create pipe");
                                        return -1;
                                }
-                               fcntl(monitor_pfd[0], F_SETFL, O_NONBLOCK);
-                               fcntl(monitor_pfd[1], F_SETFL, O_NONBLOCK);
+                               fcntl(monitor_pfd[0], F_SETFL, O_NONBLOCK); /* read end */
+                                /* NB: leave write end of pipe normal blocking mode, since
+                                   that's the same mode as for STDOUT_FILENO. */
+                               /* fcntl(monitor_pfd[1], F_SETFL, O_NONBLOCK); */ 
 #ifdef HAVE_F_SETPIPE_SZ
-                               fcntl(monitor_pfd[1], F_SETPIPE_SZ, 8*65536);
+                                /* Make it less likely for the pipe to be full. */
+                               /* fcntl(monitor_pfd[1], F_SETPIPE_SZ, 8*65536); */
 #endif
                                monitor_set = 1;
                                out_fd[avail_cpus[0]] = monitor_pfd[1];
index db3d91949cae465dc916acffb9c02c19074c712a..ea5aa285c80e3ee78c055c9d6f2b60d81858b347 100644 (file)
@@ -227,11 +227,23 @@ int openat_cloexec(int dirfd, const char *pathname, int flags, mode_t mode);
 void closefrom(int lowfd);
 
 /* monitor.c function */
+#ifdef HAVE_MONITOR_LIBS
 void monitor_winch(int signum);
 void monitor_setup(void);
 void monitor_cleanup(void);
 void monitor_render(void);
 void monitor_input(void);
+void monitor_exited(void);
+void monitor_remember_output_line(const char* buf, const size_t bytes);
+#else
+inline void monitor_winch(int signum) {(void) signum;}
+inline void monitor_setup(void) {}
+inline void monitor_cleanup(void) {}
+inline void monitor_render(void) {}
+inline void monitor_input(void) {}
+inline void monitor_exited(void) {}
+inline void monitor_remember_output_line(const char* buf, const size_t bytes) {(void)buf; (void)bytes;}
+#endif
 
 /*
  * variables
This page took 0.041572 seconds and 5 git commands to generate.