lib: make show thread... commands mt-aware

This patch fixes up show thread commands so that they know about
and operate on all extant thread_masters, since we can now have multiple
running in any given application.

This change also eliminates a heap use after free that appears when
using a single cpu_record shared among multiple threads. Since struct
thread's have pointers to bits of memory that are freed when the global
statistics hash table is freed, later accesses are invalid. By moving
the stats hash to be unique to each thread_master this problem is
sidestepped.

Signed-off-by: Quentin Young <qlyoung@cumulusnetworks.com>
This commit is contained in:
Quentin Young 2017-06-15 19:10:57 +00:00
parent e0bebc7c22
commit 62f4402239
2 changed files with 164 additions and 165 deletions

View File

@ -47,18 +47,15 @@ DEFINE_MTYPE_STATIC(LIB, THREAD_STATS, "Thread stats")
write (m->io_pipe[1], &wakebyte, 1); \ write (m->io_pipe[1], &wakebyte, 1); \
} while (0); } while (0);
/* control variable for initializer */
pthread_once_t init_once = PTHREAD_ONCE_INIT; pthread_once_t init_once = PTHREAD_ONCE_INIT;
static pthread_mutex_t cpu_record_mtx = PTHREAD_MUTEX_INITIALIZER;
static struct hash *cpu_record = NULL;
pthread_key_t thread_current; pthread_key_t thread_current;
static unsigned long pthread_mutex_t masters_mtx = PTHREAD_MUTEX_INITIALIZER;
timeval_elapsed (struct timeval a, struct timeval b) static struct list *masters;
{
return (((a.tv_sec - b.tv_sec) * TIMER_SECOND_MICRO)
+ (a.tv_usec - b.tv_usec));
}
/* CLI start ---------------------------------------------------------------- */
static unsigned int static unsigned int
cpu_record_hash_key (struct cpu_thread_history *a) cpu_record_hash_key (struct cpu_thread_history *a)
{ {
@ -108,12 +105,12 @@ vty_out_cpu_thread_history(struct vty* vty,
} }
static void static void
cpu_record_hash_print(struct hash_backet *bucket, cpu_record_hash_print(struct hash_backet *bucket, void *args[])
void *args[])
{ {
struct cpu_thread_history *totals = args[0]; struct cpu_thread_history *totals = args[0];
struct vty *vty = args[1]; struct vty *vty = args[1];
thread_type *filter = args[2]; thread_type *filter = args[2];
struct cpu_thread_history *a = bucket->data; struct cpu_thread_history *a = bucket->data;
if ( !(a->types & *filter) ) if ( !(a->types & *filter) )
@ -134,29 +131,116 @@ cpu_record_print(struct vty *vty, thread_type filter)
{ {
struct cpu_thread_history tmp; struct cpu_thread_history tmp;
void *args[3] = {&tmp, vty, &filter}; void *args[3] = {&tmp, vty, &filter};
struct thread_master *m;
struct listnode *ln;
int n = 0;
memset(&tmp, 0, sizeof tmp); memset(&tmp, 0, sizeof tmp);
tmp.funcname = "TOTAL"; tmp.funcname = "TOTAL";
tmp.types = filter; tmp.types = filter;
pthread_mutex_lock (&masters_mtx);
{
for (ALL_LIST_ELEMENTS_RO (masters, ln, m)) {
vty_out (vty, VTYNL);
vty_outln(vty, "Showing statistics for pthread %d", n++);
vty_outln(vty, "-----------------------------------------------");
vty_outln(vty, "%21s %18s %18s", vty_outln(vty, "%21s %18s %18s",
"", "CPU (user+system):", "Real (wall-clock):"); "", "CPU (user+system):", "Real (wall-clock):");
vty_out(vty, "Active Runtime(ms) Invoked Avg uSec Max uSecs"); vty_out(vty, "Active Runtime(ms) Invoked Avg uSec Max uSecs");
vty_out(vty, " Avg uSec Max uSecs"); vty_out(vty, " Avg uSec Max uSecs");
vty_outln(vty, " Type Thread"); vty_outln(vty, " Type Thread");
pthread_mutex_lock (&cpu_record_mtx); hash_iterate(m->cpu_record,
{ (void (*)(struct hash_backet *, void *))
hash_iterate(cpu_record, cpu_record_hash_print,
(void(*)(struct hash_backet*,void*))cpu_record_hash_print,
args); args);
vty_out(vty, VTYNL);
} }
pthread_mutex_unlock (&cpu_record_mtx); }
pthread_mutex_unlock (&masters_mtx);
if (tmp.total_calls > 0) if (tmp.total_calls > 0)
vty_out_cpu_thread_history(vty, &tmp); vty_out_cpu_thread_history(vty, &tmp);
} }
static void
cpu_record_hash_clear (struct hash_backet *bucket, void *args[])
{
thread_type *filter = args[0];
struct hash *cpu_record = args[1];
struct cpu_thread_history *a = bucket->data;
if ( !(a->types & *filter) )
return;
hash_release (cpu_record, bucket->data);
}
static void
cpu_record_clear (thread_type filter)
{
thread_type *tmp = &filter;
struct thread_master *m;
struct listnode *ln;
pthread_mutex_lock (&masters_mtx);
{
for (ALL_LIST_ELEMENTS_RO (masters, ln, m)) {
pthread_mutex_lock (&m->mtx);
{
void *args[2] = { tmp, m->cpu_record };
hash_iterate (m->cpu_record,
(void (*) (struct hash_backet*,void*))
cpu_record_hash_clear,
args);
}
pthread_mutex_unlock (&m->mtx);
}
}
pthread_mutex_unlock (&masters_mtx);
}
static thread_type
parse_filter (const char *filterstr)
{
int i = 0;
int filter = 0;
while (filterstr[i] != '\0')
{
switch (filterstr[i])
{
case 'r':
case 'R':
filter |= (1 << THREAD_READ);
break;
case 'w':
case 'W':
filter |= (1 << THREAD_WRITE);
break;
case 't':
case 'T':
filter |= (1 << THREAD_TIMER);
break;
case 'e':
case 'E':
filter |= (1 << THREAD_EVENT);
break;
case 'x':
case 'X':
filter |= (1 << THREAD_EXECUTE);
break;
default:
break;
}
++i;
}
return filter;
}
DEFUN (show_thread_cpu, DEFUN (show_thread_cpu,
show_thread_cpu_cmd, show_thread_cpu_cmd,
"show thread cpu [FILTER]", "show thread cpu [FILTER]",
@ -165,47 +249,14 @@ DEFUN (show_thread_cpu,
"Thread CPU usage\n" "Thread CPU usage\n"
"Display filter (rwtexb)\n") "Display filter (rwtexb)\n")
{ {
int idx_filter = 3;
int i = 0;
thread_type filter = (thread_type) -1U; thread_type filter = (thread_type) -1U;
int idx = 0;
if (argc > 3) if (argv_find (argv, argc, "FILTER", &idx)) {
{ filter = parse_filter (argv[idx]->arg);
filter = 0; if (!filter) {
while (argv[idx_filter]->arg[i] != '\0') vty_outln(vty, "Invalid filter \"%s\" specified; must contain at least"
{ "one of 'RWTEXB'%s", argv[idx]->arg);
switch ( argv[idx_filter]->arg[i] )
{
case 'r':
case 'R':
filter |= (1 << THREAD_READ);
break;
case 'w':
case 'W':
filter |= (1 << THREAD_WRITE);
break;
case 't':
case 'T':
filter |= (1 << THREAD_TIMER);
break;
case 'e':
case 'E':
filter |= (1 << THREAD_EVENT);
break;
case 'x':
case 'X':
filter |= (1 << THREAD_EXECUTE);
break;
default:
break;
}
++i;
}
if (filter == 0)
{
vty_outln (vty, "Invalid filter \"%s\" specified,"
" must contain at least one of 'RWTEXB'",
argv[idx_filter]->arg);
return CMD_WARNING; return CMD_WARNING;
} }
} }
@ -214,86 +265,22 @@ DEFUN (show_thread_cpu,
return CMD_SUCCESS; return CMD_SUCCESS;
} }
static void
cpu_record_hash_clear (struct hash_backet *bucket,
void *args)
{
thread_type *filter = args;
struct cpu_thread_history *a = bucket->data;
if ( !(a->types & *filter) )
return;
pthread_mutex_lock (&cpu_record_mtx);
{
hash_release (cpu_record, bucket->data);
}
pthread_mutex_unlock (&cpu_record_mtx);
}
static void
cpu_record_clear (thread_type filter)
{
thread_type *tmp = &filter;
pthread_mutex_lock (&cpu_record_mtx);
{
hash_iterate (cpu_record,
(void (*) (struct hash_backet*,void*)) cpu_record_hash_clear,
tmp);
}
pthread_mutex_unlock (&cpu_record_mtx);
}
DEFUN (clear_thread_cpu, DEFUN (clear_thread_cpu,
clear_thread_cpu_cmd, clear_thread_cpu_cmd,
"clear thread cpu [FILTER]", "clear thread cpu [FILTER]",
"Clear stored data\n" "Clear stored data in all pthreads\n"
"Thread information\n" "Thread information\n"
"Thread CPU usage\n" "Thread CPU usage\n"
"Display filter (rwtexb)\n") "Display filter (rwtexb)\n")
{ {
int idx_filter = 3;
int i = 0;
thread_type filter = (thread_type) -1U; thread_type filter = (thread_type) -1U;
int idx = 0;
if (argc > 3) if (argv_find (argv, argc, "FILTER", &idx)) {
{ filter = parse_filter (argv[idx]->arg);
filter = 0; if (!filter) {
while (argv[idx_filter]->arg[i] != '\0') vty_outln(vty, "Invalid filter \"%s\" specified; must contain at least"
{ "one of 'RWTEXB'%s", argv[idx]->arg);
switch ( argv[idx_filter]->arg[i] )
{
case 'r':
case 'R':
filter |= (1 << THREAD_READ);
break;
case 'w':
case 'W':
filter |= (1 << THREAD_WRITE);
break;
case 't':
case 'T':
filter |= (1 << THREAD_TIMER);
break;
case 'e':
case 'E':
filter |= (1 << THREAD_EVENT);
break;
case 'x':
case 'X':
filter |= (1 << THREAD_EXECUTE);
break;
default:
break;
}
++i;
}
if (filter == 0)
{
vty_outln (vty, "Invalid filter \"%s\" specified,"
" must contain at least one of 'RWTEXB'",
argv[idx_filter]->arg);
return CMD_WARNING; return CMD_WARNING;
} }
} }
@ -308,6 +295,8 @@ thread_cmd_init (void)
install_element (VIEW_NODE, &show_thread_cpu_cmd); install_element (VIEW_NODE, &show_thread_cpu_cmd);
install_element (ENABLE_NODE, &clear_thread_cpu_cmd); install_element (ENABLE_NODE, &clear_thread_cpu_cmd);
} }
/* CLI end ------------------------------------------------------------------ */
static int static int
thread_timer_cmp(void *a, void *b) thread_timer_cmp(void *a, void *b)
@ -339,10 +328,8 @@ cancelreq_del (void *cr)
/* initializer, only ever called once */ /* initializer, only ever called once */
static void initializer () static void initializer ()
{ {
if (cpu_record == NULL) if (!masters)
cpu_record = hash_create ((unsigned int (*) (void *))cpu_record_hash_key, masters = list_new();
(int (*) (const void *, const void *))
cpu_record_hash_cmp);
pthread_key_create (&thread_current, NULL); pthread_key_create (&thread_current, NULL);
} }
@ -354,17 +341,18 @@ thread_master_create (void)
struct thread_master *rv; struct thread_master *rv;
struct rlimit limit; struct rlimit limit;
getrlimit(RLIMIT_NOFILE, &limit);
pthread_once (&init_once, &initializer); pthread_once (&init_once, &initializer);
rv = XCALLOC (MTYPE_THREAD_MASTER, sizeof (struct thread_master)); rv = XCALLOC (MTYPE_THREAD_MASTER, sizeof (struct thread_master));
if (rv == NULL) if (rv == NULL)
return NULL; return NULL;
/* Initialize master mutex */
pthread_mutex_init (&rv->mtx, NULL); pthread_mutex_init (&rv->mtx, NULL);
pthread_cond_init (&rv->cancel_cond, NULL); pthread_cond_init (&rv->cancel_cond, NULL);
/* Initialize I/O task data structures */
getrlimit(RLIMIT_NOFILE, &limit);
rv->fd_limit = (int)limit.rlim_cur; rv->fd_limit = (int)limit.rlim_cur;
rv->read = XCALLOC (MTYPE_THREAD, sizeof (struct thread *) * rv->fd_limit); rv->read = XCALLOC (MTYPE_THREAD, sizeof (struct thread *) * rv->fd_limit);
if (rv->read == NULL) if (rv->read == NULL)
@ -372,7 +360,6 @@ thread_master_create (void)
XFREE (MTYPE_THREAD_MASTER, rv); XFREE (MTYPE_THREAD_MASTER, rv);
return NULL; return NULL;
} }
rv->write = XCALLOC (MTYPE_THREAD, sizeof (struct thread *) * rv->fd_limit); rv->write = XCALLOC (MTYPE_THREAD, sizeof (struct thread *) * rv->fd_limit);
if (rv->write == NULL) if (rv->write == NULL)
{ {
@ -381,20 +368,32 @@ thread_master_create (void)
return NULL; return NULL;
} }
rv->cpu_record = hash_create ((unsigned int (*) (void *))cpu_record_hash_key,
(int (*) (const void *, const void *))
cpu_record_hash_cmp);
/* Initialize the timer queues */ /* Initialize the timer queues */
rv->timer = pqueue_create(); rv->timer = pqueue_create();
rv->timer->cmp = thread_timer_cmp; rv->timer->cmp = thread_timer_cmp;
rv->timer->update = thread_timer_update; rv->timer->update = thread_timer_update;
/* Initialize thread_fetch() settings */
rv->spin = true; rv->spin = true;
rv->handle_signals = true; rv->handle_signals = true;
/* Set pthread owner, should be updated by actual owner */
rv->owner = pthread_self(); rv->owner = pthread_self();
rv->cancel_req = list_new (); rv->cancel_req = list_new ();
rv->cancel_req->del = cancelreq_del; rv->cancel_req->del = cancelreq_del;
rv->canceled = true; rv->canceled = true;
/* Initialize pipe poker */
pipe (rv->io_pipe); pipe (rv->io_pipe);
set_nonblocking (rv->io_pipe[0]); set_nonblocking (rv->io_pipe[0]);
set_nonblocking (rv->io_pipe[1]); set_nonblocking (rv->io_pipe[1]);
/* Initialize data structures for poll() */
rv->handler.pfdsize = rv->fd_limit; rv->handler.pfdsize = rv->fd_limit;
rv->handler.pfdcount = 0; rv->handler.pfdcount = 0;
rv->handler.pfds = XCALLOC (MTYPE_THREAD_MASTER, rv->handler.pfds = XCALLOC (MTYPE_THREAD_MASTER,
@ -402,6 +401,13 @@ thread_master_create (void)
rv->handler.copy = XCALLOC (MTYPE_THREAD_MASTER, rv->handler.copy = XCALLOC (MTYPE_THREAD_MASTER,
sizeof (struct pollfd) * rv->handler.pfdsize); sizeof (struct pollfd) * rv->handler.pfdsize);
/* add to list */
pthread_mutex_lock (&masters_mtx);
{
listnode_add (masters, rv);
}
pthread_mutex_unlock (&masters_mtx);
return rv; return rv;
} }
@ -551,20 +557,13 @@ thread_master_free (struct thread_master *m)
close (m->io_pipe[1]); close (m->io_pipe[1]);
list_delete (m->cancel_req); list_delete (m->cancel_req);
hash_clean (m->cpu_record, cpu_record_hash_free);
hash_free (m->cpu_record);
m->cpu_record = NULL;
XFREE (MTYPE_THREAD_MASTER, m->handler.pfds); XFREE (MTYPE_THREAD_MASTER, m->handler.pfds);
XFREE (MTYPE_THREAD_MASTER, m->handler.copy); XFREE (MTYPE_THREAD_MASTER, m->handler.copy);
XFREE (MTYPE_THREAD_MASTER, m); XFREE (MTYPE_THREAD_MASTER, m);
pthread_mutex_lock (&cpu_record_mtx);
{
if (cpu_record)
{
hash_clean (cpu_record, cpu_record_hash_free);
hash_free (cpu_record);
cpu_record = NULL;
}
}
pthread_mutex_unlock (&cpu_record_mtx);
} }
/* Return remain time in second. */ /* Return remain time in second. */
@ -636,13 +635,9 @@ thread_get (struct thread_master *m, u_char type,
{ {
tmp.func = func; tmp.func = func;
tmp.funcname = funcname; tmp.funcname = funcname;
pthread_mutex_lock (&cpu_record_mtx); thread->hist = hash_get (m->cpu_record, &tmp,
{
thread->hist = hash_get (cpu_record, &tmp,
(void * (*) (void *))cpu_record_hash_alloc); (void * (*) (void *))cpu_record_hash_alloc);
} }
pthread_mutex_unlock (&cpu_record_mtx);
}
thread->hist->total_active++; thread->hist->total_active++;
thread->func = func; thread->func = func;
thread->funcname = funcname; thread->funcname = funcname;
@ -1408,6 +1403,13 @@ thread_fetch (struct thread_master *m, struct thread *fetch)
return fetch; return fetch;
} }
static unsigned long
timeval_elapsed (struct timeval a, struct timeval b)
{
return (((a.tv_sec - b.tv_sec) * TIMER_SECOND_MICRO)
+ (a.tv_usec - b.tv_usec));
}
unsigned long unsigned long
thread_consumed_time (RUSAGE_T *now, RUSAGE_T *start, unsigned long *cputime) thread_consumed_time (RUSAGE_T *now, RUSAGE_T *start, unsigned long *cputime)
{ {
@ -1523,12 +1525,8 @@ funcname_thread_execute (struct thread_master *m,
tmp.func = dummy.func = func; tmp.func = dummy.func = func;
tmp.funcname = dummy.funcname = funcname; tmp.funcname = dummy.funcname = funcname;
pthread_mutex_lock (&cpu_record_mtx); dummy.hist = hash_get (m->cpu_record, &tmp,
{
dummy.hist = hash_get (cpu_record, &tmp,
(void * (*) (void *))cpu_record_hash_alloc); (void * (*) (void *))cpu_record_hash_alloc);
}
pthread_mutex_unlock (&cpu_record_mtx);
dummy.schedfrom = schedfrom; dummy.schedfrom = schedfrom;
dummy.schedfrom_line = fromln; dummy.schedfrom_line = fromln;

View File

@ -80,6 +80,7 @@ struct thread_master
struct list *cancel_req; struct list *cancel_req;
bool canceled; bool canceled;
pthread_cond_t cancel_cond; pthread_cond_t cancel_cond;
struct hash *cpu_record;
int io_pipe[2]; int io_pipe[2];
int fd_limit; int fd_limit;
struct fd_handler handler; struct fd_handler handler;