]> granicus.if.org Git - procps-ng/commitdiff
top: introduce background updates via separate threads
authorJim Warner <james.warner@comcast.net>
Sat, 18 Sep 2021 05:00:00 +0000 (00:00 -0500)
committerCraig Small <csmall@dropbear.xyz>
Mon, 20 Sep 2021 10:05:44 +0000 (20:05 +1000)
After the stage had been set in the previous patch, in
this patch we will actually implement those background
updates via 3 separate threads. The design was simple:

. the do-while loops have now been made truly infinite
. 2 semaphores per thread allow needed synchronization
. 1 semaphore will provide for each thread to sem_wait
. 1 semaphore will provide for display o/p to sem_wait
. and all 3 thread's program name was made descriptive

A complication was the potential for a signal directed
to one of our new threads. Rather than having a thread
try to deal with such signals, we pass a mask with all
signals blocked at pthread_create time. Thereafter any
subsequent signals are forwarded to the parent thread.

[ also sigprocmask was exchanged for pthread_sigmask ]
[ since warned about use "in multithreaded process". ]

[ plus we also modified each of those POSIX comments ]
[ about 2004 to agree with current signal-safety(7). ]

Sadly, after all this effort there were no performance
benefits to having separate threads. In fact there was
a measurable performance degradation when running with
ever smaller delay intervals. But even with a delay of
1/10 second the 'real' cost increase is only about 1%.

There is one way whereby any additional costs might be
eliminated (at least seemingly). One could introduce 2
separate sets of contexts for each of those 3 threads.
Then retrieval & display could be overlapped. However,
the resulting display wouldn't represent the real-time
results. Rather it would be stale by 1 delay interval.

Signed-off-by: Jim Warner <james.warner@comcast.net>
top/top.c
top/top.h
top/top_nls.c
top/top_nls.h

index fabf3d74ca58423fb3e910561993a4164967b470..18c660f0a09d674e538673528cfbff2a43decde1 100644 (file)
--- a/top/top.c
+++ b/top/top.c
@@ -24,6 +24,8 @@
 #include <getopt.h>
 #include <limits.h>
 #include <pwd.h>
+#include <pthread.h>
+#include <semaphore.h>
 #include <signal.h>
 #include <stdarg.h>
 #include <stdio.h>
@@ -269,6 +271,21 @@ enum Rel_memitems {
    swp_TOT, swp_FRE, swp_USE };
         // mem stack results extractor macro, where e=rel enum
 #define MEM_VAL(e) MEMINFO_VAL(e, ul_int, Mem_stack, Mem_ctx)
+
+        /* Support for concurrent library updates via
+           multithreaded background processes */
+#ifndef THREADNO_CPU
+static pthread_t Thread_id_cpus;
+static sem_t Semaphore_cpus_beg, Semaphore_cpus_end;
+#endif
+#ifndef THREADNO_MEM
+static pthread_t Thread_id_memory;
+static sem_t Semaphore_memory_beg, Semaphore_memory_end;
+#endif
+#ifndef THREADNO_TSK
+static pthread_t Thread_id_tasks;
+static sem_t Semaphore_tasks_beg, Semaphore_tasks_end;
+#endif
 \f
 /*######  Tiny useful routine(s)  ########################################*/
 
@@ -348,9 +365,9 @@ static void bye_bye (const char *str) __attribute__((__noreturn__));
 static void bye_bye (const char *str) {
    sigset_t ss;
 
-// POSIX.1-2004 async-signal-safe: sigfillset, sigprocmask
+// POSIX.1 async-signal-safe: sigfillset, pthread_sigmask
    sigfillset(&ss);
-   sigprocmask(SIG_BLOCK, &ss, NULL);
+   pthread_sigmask(SIG_BLOCK, &ss, NULL);
    at_eoj();                 // restore tty in preparation for exit
 #ifdef ATEOJ_RPTSTD
 {
@@ -419,6 +436,24 @@ static void bye_bye (const char *str) {
 
    // there's lots of signal-unsafe stuff in the following ...
    if (Frames_signal != BREAK_sig) {
+#ifndef THREADNO_CPU
+      pthread_cancel(Thread_id_cpus);
+      pthread_join(Thread_id_cpus, NULL);
+      sem_destroy(&Semaphore_cpus_beg);
+      sem_destroy(&Semaphore_cpus_end);
+#endif
+#ifndef THREADNO_MEM
+      pthread_cancel(Thread_id_memory);
+      pthread_join(Thread_id_memory, NULL);
+      sem_destroy(&Semaphore_memory_beg);
+      sem_destroy(&Semaphore_memory_end);
+#endif
+#ifndef THREADNO_TSK
+      pthread_cancel(Thread_id_tasks);
+      pthread_join(Thread_id_tasks, NULL);
+      sem_destroy(&Semaphore_tasks_end);
+      sem_destroy(&Semaphore_tasks_beg);
+#endif
       procps_pids_unref(&Pids_ctx);
       procps_stat_unref(&Stat_ctx);
       procps_meminfo_unref(&Mem_ctx);
@@ -462,16 +497,16 @@ static void sig_abexit (int sig) __attribute__((__noreturn__));
 static void sig_abexit (int sig) {
    sigset_t ss;
 
-// POSIX.1-2004 async-signal-safe: sigfillset, sigprocmask, signal, sigemptyset, sigaddset, raise
+// POSIX.1 async-signal-safe: sigfillset, signal, sigemptyset, sigaddset, pthread_sigmask, raise
    sigfillset(&ss);
-   sigprocmask(SIG_BLOCK, &ss, NULL);
+   pthread_sigmask(SIG_BLOCK, &ss, NULL);
    at_eoj();                 // restore tty in preparation for exit
    fprintf(stderr, N_fmt(EXIT_signals_fmt)
       , sig, signal_number_to_name(sig), Myname);
    signal(sig, SIG_DFL);     // allow core dumps, if applicable
    sigemptyset(&ss);
    sigaddset(&ss, sig);
-   sigprocmask(SIG_UNBLOCK, &ss, NULL);
+   pthread_sigmask(SIG_UNBLOCK, &ss, NULL);
    raise(sig);               // ( plus set proper return code )
    _exit(EXIT_FAILURE);      // if default sig action is ignore
 } // end: sig_abexit
@@ -493,7 +528,7 @@ static void sig_endpgm (int dont_care_sig) {
          * Catches:
          *    SIGTSTP, SIGTTIN and SIGTTOU */
 static void sig_paused (int dont_care_sig) {
-// POSIX.1-2004 async-signal-safe: tcsetattr, tcdrain, raise
+// POSIX.1 async-signal-safe: tcsetattr, tcdrain, raise
    if (-1 == tcsetattr(STDIN_FILENO, TCSAFLUSH, &Tty_original))
       error_exit(fmtmk(N_fmt(FAIL_tty_set_fmt), strerror(errno)));
    if (keypad_local) putp(keypad_local);
@@ -524,7 +559,7 @@ static void sig_paused (int dont_care_sig) {
          * Catches:
          *    SIGCONT and SIGWINCH */
 static void sig_resize (int dont_care_sig) {
-// POSIX.1-2004 async-signal-safe: tcdrain
+// POSIX.1 async-signal-safe: tcdrain
    tcdrain(STDOUT_FILENO);
    Frames_signal = BREAK_sig;
    (void)dont_care_sig;
@@ -2350,7 +2385,7 @@ static void zap_fieldstab (void) {
  #undef maX
 } // end: zap_fieldstab
 \f
-/*######  Library Interface  #############################################*/
+/*######  Library Interface (as separate threads)  #######################*/
 
         /*
          * This guy's responsible for interfacing with the library <stat> API
@@ -2360,6 +2395,9 @@ static void *cpus_refresh (void *unused) {
    enum stat_reap_type which;
 
    do {
+#ifndef THREADNO_CPU
+      sem_wait(&Semaphore_cpus_beg);
+#endif
       which = STAT_REAP_CPUS_ONLY;
       if (CHKw(Curwin, View_CPUNOD))
          which = STAT_REAP_NUMA_NODES_TOO;
@@ -2380,7 +2418,12 @@ static void *cpus_refresh (void *unused) {
          Cpu_cnt = 48;
 #endif
       }
+#ifndef THREADNO_CPU
+      sem_post(&Semaphore_cpus_end);
+   } while (1);
+#else
    } while (0);
+#endif
    return NULL;
    (void)unused;
 } // end: cpus_refresh
@@ -2394,6 +2437,9 @@ static void *memory_refresh (void *unused) {
    time_t cur_secs;
 
    do {
+#ifndef THREADNO_MEM
+      sem_wait(&Semaphore_memory_beg);
+#endif
       if (Frames_signal)
          sav_secs = 0;
       cur_secs = time(NULL);
@@ -2403,7 +2449,12 @@ static void *memory_refresh (void *unused) {
             error_exit(fmtmk(N_fmt(LIB_errormem_fmt),__LINE__, strerror(errno)));
          sav_secs = cur_secs;
       }
+#ifndef THREADNO_MEM
+      sem_post(&Semaphore_memory_end);
+   } while (1);
+#else
    } while (0);
+#endif
    return NULL;
    (void)unused;
 } // end: memory_refresh
@@ -2423,6 +2474,9 @@ static void *tasks_refresh (void *unused) {
    int i, what;
 
    do {
+#ifndef THREADNO_TSK
+      sem_wait(&Semaphore_tasks_beg);
+#endif
       procps_uptime(&uptime_cur, NULL);
       et = uptime_cur - uptime_sav;
       if (et < 0.01) et = 0.005;
@@ -2451,7 +2505,12 @@ static void *tasks_refresh (void *unused) {
          for (i = 0; i < GROUPSMAX; i++)
             memcpy(Winstk[i].ppt, Pids_reap->stacks, sizeof(void *) * PIDSmaxt);
       }
+#ifndef THREADNO_TSK
+      sem_post(&Semaphore_tasks_end);
+   } while (1);
+#else
    } while (0);
+#endif
    return NULL;
    (void)unused;
  #undef nALIGN
@@ -3310,6 +3369,36 @@ static void before (char *me) {
    if ((rc = procps_pids_new(&Pids_ctx, Pids_itms, Pids_itms_tot)))
       error_exit(fmtmk(N_fmt(LIB_errorpid_fmt),__LINE__, strerror(-rc)));
 
+   /* in case any of our threads have neen enabled, they'll inherit this mask
+      with everything blocked. therefore, signals go to the main thread (us). */
+   sigfillset(&sa.sa_mask);
+   pthread_sigmask(SIG_BLOCK, &sa.sa_mask, NULL);
+
+#ifndef THREADNO_CPU
+   if (0 != sem_init(&Semaphore_cpus_beg, 0, 0)
+   || (0 != sem_init(&Semaphore_cpus_end, 0, 0)))
+      error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt),__LINE__, strerror(errno)));
+   if (0 != pthread_create(&Thread_id_cpus, NULL, cpus_refresh, NULL))
+      error_exit(fmtmk(N_fmt(X_THREADINGS_fmt),__LINE__, strerror(errno)));
+   pthread_setname_np(Thread_id_cpus, "update cpus");
+#endif
+#ifndef THREADNO_MEM
+   if (0 != sem_init(&Semaphore_memory_beg, 0, 0)
+   || (0 != sem_init(&Semaphore_memory_end, 0, 0)))
+      error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt),__LINE__, strerror(errno)));
+   if (0 != pthread_create(&Thread_id_memory, NULL, memory_refresh, NULL))
+      error_exit(fmtmk(N_fmt(X_THREADINGS_fmt),__LINE__, strerror(errno)));
+   pthread_setname_np(Thread_id_memory, "update memory");
+#endif
+#ifndef THREADNO_TSK
+   if (0 != sem_init(&Semaphore_tasks_beg, 0, 0)
+   || (0 != sem_init(&Semaphore_tasks_end, 0, 0)))
+      error_exit(fmtmk(N_fmt(X_SEMAPHORES_fmt),__LINE__, strerror(errno)));
+   if (0 != pthread_create(&Thread_id_tasks, NULL, tasks_refresh, NULL))
+      error_exit(fmtmk(N_fmt(X_THREADINGS_fmt),__LINE__, strerror(errno)));
+   pthread_setname_np(Thread_id_tasks, "update tasks");
+#endif
+
 #ifndef SIGRTMAX       // not available on hurd, maybe others too
 #define SIGRTMAX 32
 #endif
@@ -5623,6 +5712,12 @@ static void summary_show (void) {
       Msg_row += 1;
    } // end: View_LOADAV
 
+#ifdef THREADED_CPU
+   sem_wait(&Semaphore_cpus_end);
+#endif
+#ifdef THREADED_TSK
+   sem_wait(&Semaphore_tasks_end);
+#endif
    // Display Task and Cpu(s) States
    if (isROOM(View_STATES, 2)) {
       show_special(0, fmtmk(N_unq(STATE_line_1_fmt)
@@ -5711,6 +5806,9 @@ numa_oops:
       }
    } // end: View_STATES
 
+#ifdef THREADED_MEM
+   sem_wait(&Semaphore_memory_end);
+#endif
    // Display Memory and Swap stats
    if (isROOM(View_MEMORY, 2)) {
     #define bfT(n)  buftab[n].buf
@@ -6268,15 +6366,32 @@ static void frame_make (void) {
 
    // whoa either first time or thread/task mode change, (re)prime the pump...
    if (Pseudo_row == PROC_XTRA) {
+#ifndef THREADNO_TSK
+      sem_post(&Semaphore_tasks_beg);
+      sem_wait(&Semaphore_tasks_end);
+#else
       tasks_refresh(NULL);
+#endif
       usleep(LIB_USLEEP);
       putp(Cap_clr_scr);
    } else
       putp(Batch ? "\n\n" : Cap_home);
 
+#ifndef THREADNO_TSK
+   sem_post(&Semaphore_tasks_beg);
+#else
+   tasks_refresh(NULL);
+#endif
+#ifndef THREADNO_CPU
+   sem_post(&Semaphore_cpus_beg);
+#else
    cpus_refresh(NULL);
+#endif
+#ifndef THREADNO_MEM
+   sem_post(&Semaphore_memory_beg);
+#else
    memory_refresh(NULL);
-   tasks_refresh(NULL);
+#endif
 
    Tree_idx = Pseudo_row = Msg_row = scrlins = 0;
    summary_show();
index fcd839bab959448c55629967ebecd8c3b5790d23..23d0aadd7588ac3228c92e380e7c3a8dc9562b6d 100644 (file)
--- a/top/top.h
+++ b/top/top.h
@@ -50,6 +50,9 @@
 //#define SCROLLV_BY_1            /* when scrolling left/right do not move 8 */
 //#define STRINGCASENO            /* case insenstive compare/locate versions */
 //#define TERMIOS_ONLY            /* use native input only (just limp along) */
+//#define THREADNO_CPU            /* suppress background thread for cpu updt */
+//#define THREADNO_MEM            /* suppress background thread for mem updt */
+//#define THREADNO_TSK            /* suppress background thread for tsk updt */
 //#define TOG4_NOFORCE            /* no force 2 abreast mode with '4' toggle */
 //#define TOG4_NOTRUNC            /* ensure no truncation for 2 abreast mode */
 //#define TOG4_OFF_MEM            /* don't show two abreast memory statistic */
@@ -627,7 +630,7 @@ typedef struct WIN_t {
 //atic void          fields_utility (void);
 //atic inline void   widths_resize (void);
 //atic void          zap_fieldstab (void);
-/*------  Library Interface  ---------------------------------------------*/
+/*------  Library Interface (as separate threads)  -----------------------*/
 //atic void         *cpus_refresh (void *unused);
 //atic void         *memory_refresh (void *unused);
 //atic void         *tasks_refresh (void *unused);
index 259eec272dca41a46fdfaaa2940aa630a1cebd52..c2db9b9342dafecda9503a5dfeb9fd8fa237cfd4 100644 (file)
@@ -569,6 +569,8 @@ static void build_norm_nlstab (void) {
    Norm_nlstab[XTRA_size2up_txt] = _("terminal is not wide enough");
    Norm_nlstab[XTRA_modebad_txt] = _("wrong mode, command inactive");
    Norm_nlstab[XTRA_warnold_txt] = _("saving prevents older top from reading, save anyway?");
+   Norm_nlstab[X_SEMAPHORES_fmt] = _("failed sem_init() at %d: %s");
+   Norm_nlstab[X_THREADINGS_fmt] = _("failed pthread_create() at %d: %s");
 }
 
 
index df38c77879935a0553c77a182dee5b2b8bc8da51..4995f17771abac170515684ae28697bd81dad51b 100644 (file)
@@ -84,6 +84,7 @@ enum norm_nls {
    WORD_process_txt, WORD_threads_txt, WRITE_rcfile_fmt, WRONG_switch_fmt,
    XTRA_badflds_fmt, XTRA_fixwide_fmt, XTRA_modebad_txt, XTRA_size2up_txt,
    XTRA_vforest_fmt, XTRA_warncfg_txt, XTRA_warnold_txt, XTRA_winsize_txt,
+   X_SEMAPHORES_fmt, X_THREADINGS_fmt,
 #ifndef INSP_OFFDEMO
    YINSP_demo01_txt, YINSP_demo02_txt, YINSP_demo03_txt, YINSP_deqfmt_txt,
    YINSP_deqtyp_txt, YINSP_dstory_txt,