top: introduce background updates via separate threads

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>
This commit is contained in:
Jim Warner 2021-09-18 00:00:00 -05:00 committed by Craig Small
parent b4b68f11d6
commit 29f0a674a8
4 changed files with 132 additions and 11 deletions

135
top/top.c
View File

@ -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
/*###### 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
/*###### 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);
cpus_refresh(NULL);
memory_refresh(NULL);
#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);
#endif
Tree_idx = Pseudo_row = Msg_row = scrlins = 0;
summary_show();

View File

@ -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);

View 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");
}

View 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,