Blame SOURCES/redhat-bugzilla-1721107.patch

8b506c
commit 775bd386c2b9a6e553c61a5c0f86d29d9c2320a8
8b506c
Author: Mark Goodwin <mgoodwin@redhat.com>
8b506c
Date:   Thu Jul 25 13:30:05 2019 +1000
8b506c
8b506c
    pmdaproc: fix memory leak in pidlist refresh
8b506c
    
8b506c
    RHBZ #1721107
8b506c
    
8b506c
    The pidlist refresh function was leaking instance name buffers for
8b506c
    processes that had exited between refreshes. Fix this by storing the
8b506c
    instance name (truncated psargs) in a new field of the hash table
8b506c
    entry, and harvest them correctly if they've exited after each refresh.
8b506c
    The indom table instance names now share a pointer to the new field
8b506c
    in the hash table entry so they dont need to be separately free'd.
8b506c
    This similifies the code and memory management.
8b506c
    
8b506c
    Also put a 4K cap on the maximum length of a psargs string for the
8b506c
    unlikely scenario of a psargs string that is not NULL terminated,
8b506c
    i.e. use strndup() rather than strdup(). This may be possible if
8b506c
    a rogue process modified it's argv[0] at run-time.
8b506c
    
8b506c
    For testing, the recipe in RHBZ#1721107 was repro'd using valgind
8b506c
    and a workload with lots of process churn (i.e. a couple of large
8b506c
    builds running). Before the fix, valgrind reported numerous similar
8b506c
    leaks as reported in the bug. With the fix applied, those leaks
8b506c
    are gone. The refresh is more efficient now too because we only
8b506c
    have one copy of the psargs strings (in the hash table entry).
8b506c
    
8b506c
    Also thoroughly exercised all tests in the QA pmda.proc and
8b506c
    pmda.hotproc groups. A new test might be possible, but the nature
8b506c
    of the required workload would make it difficult (and expensive)
8b506c
    to run.
8b506c
8b506c
diff --git a/src/pmdas/linux_proc/proc_pid.c b/src/pmdas/linux_proc/proc_pid.c
8b506c
index 2e8f705b9..dcbc28850 100644
8b506c
--- a/src/pmdas/linux_proc/proc_pid.c
8b506c
+++ b/src/pmdas/linux_proc/proc_pid.c
8b506c
@@ -820,7 +820,7 @@ disable_hotproc(void)
8b506c
 static void
8b506c
 refresh_proc_pidlist(proc_pid_t *proc_pid, proc_pid_list_t *pids)
8b506c
 {
8b506c
-    int i;
8b506c
+    int i, numinst, idx;
8b506c
     int fd;
8b506c
     char *p;
8b506c
     char buf[MAXPATHLEN];
8b506c
@@ -828,14 +828,6 @@ refresh_proc_pidlist(proc_pid_t *proc_pid, proc_pid_list_t *pids)
8b506c
     proc_pid_entry_t *ep;
8b506c
     pmdaIndom *indomp = proc_pid->indom;
8b506c
 
8b506c
-    if (indomp->it_numinst < pids->count) {
8b506c
-	indomp->it_set = (pmdaInstid *)realloc(indomp->it_set,
8b506c
-	    pids->count * sizeof(pmdaInstid));
8b506c
-    	memset(&indomp->it_set[indomp->it_numinst], 0,
8b506c
-	    (pids->count - indomp->it_numinst) * sizeof(pmdaInstid));
8b506c
-    }
8b506c
-    indomp->it_numinst = pids->count;
8b506c
-
8b506c
     /*
8b506c
      * invalidate all entries so we can harvest pids that have exited
8b506c
      */
8b506c
@@ -852,7 +844,9 @@ refresh_proc_pidlist(proc_pid_t *proc_pid, proc_pid_list_t *pids)
8b506c
      */
8b506c
     for (i=0; i < pids->count; i++) {
8b506c
 	node = __pmHashSearch(pids->pids[i], &proc_pid->pidhash);
8b506c
-	if (node == NULL) {
8b506c
+	if (node)
8b506c
+	    ep = (proc_pid_entry_t *)node->data;
8b506c
+	else {
8b506c
 	    int k = 0;
8b506c
 
8b506c
 	    ep = (proc_pid_entry_t *)malloc(sizeof(proc_pid_entry_t));
8b506c
@@ -940,57 +934,52 @@ refresh_proc_pidlist(proc_pid_t *proc_pid, proc_pid_list_t *pids)
8b506c
 	    __pmHashAdd(pids->pids[i], (void *)ep, &proc_pid->pidhash);
8b506c
 	    //fprintf(stderr, "key %d : ADDED \"%s\" to hash table\n", pids->pids[i], buf);
8b506c
 	}
8b506c
-	else
8b506c
-	    ep = (proc_pid_entry_t *)node->data;
8b506c
-	
8b506c
-	/* mark pid as still existing */
8b506c
-	ep->flags |= PROC_PID_FLAG_VALID;
8b506c
 
8b506c
-	/* refresh the indom pointer */
8b506c
-	indomp->it_set[i].i_inst = ep->id;
8b506c
-	if (indomp->it_set[i].i_name) {
8b506c
-	    int len = strlen(indomp->it_set[i].i_name);
8b506c
-	    if (strncmp(indomp->it_set[i].i_name, ep->name, len) != 0) {
8b506c
-	    	free(indomp->it_set[i].i_name);
8b506c
-	    	indomp->it_set[i].i_name = NULL;
8b506c
-	    }
8b506c
-	    else if (pmDebugOptions.libpmda) {
8b506c
-		fprintf(stderr, "refresh_proc_pidlist: Instance id=%d \"%s\" no change\n",
8b506c
-		    ep->id, indomp->it_set[i].i_name);
8b506c
-	    }
8b506c
-	}
8b506c
-	if (indomp->it_set[i].i_name == NULL) {
8b506c
-	    /*
8b506c
-	     * The external instance name is the pid followed by
8b506c
-	     * a copy of the psargs truncated at the first space.
8b506c
-	     * e.g. "012345 /path/to/command". Command line args,
8b506c
-	     * if any, are truncated. The full command line is
8b506c
-	     * available in the proc.psinfo.psargs metric.
8b506c
-	     */
8b506c
-	    if ((p = strchr(ep->name, ' ')) != NULL) {
8b506c
-		if ((p = strchr(p+1, ' ')) != NULL) {
8b506c
-		    int len = p - ep->name;
8b506c
-		    indomp->it_set[i].i_name = (char *)malloc(len+1);
8b506c
-		    strncpy(indomp->it_set[i].i_name, ep->name, len);
8b506c
-		    indomp->it_set[i].i_name[len] = '\0';
8b506c
-		}
8b506c
-	    }
8b506c
-	    if (indomp->it_set[i].i_name == NULL)
8b506c
-		indomp->it_set[i].i_name = strdup(ep->name);
8b506c
+	if (ep->instname == NULL) {
8b506c
+           /*
8b506c
+             * The external instance name is the pid followed by
8b506c
+             * a copy of the psargs truncated at the first space.
8b506c
+             * e.g. "012345 /path/to/command". Command line args,
8b506c
+             * if any, are truncated. The full command line is
8b506c
+             * available in the proc.psinfo.psargs metric.
8b506c
+             */
8b506c
+            if ((p = strchr(ep->name, ' ')) != NULL) {
8b506c
+                if ((p = strchr(p+1, ' ')) != NULL) {
8b506c
+                    int len = p - ep->name;
8b506c
+		    if (len > PROC_PID_STAT_CMD_MAXLEN)
8b506c
+			len = PROC_PID_STAT_CMD_MAXLEN;
8b506c
+                    ep->instname = (char *)malloc(len+1);
8b506c
+                    strncpy(ep->instname, ep->name, len);
8b506c
+                    ep->instname[len] = '\0';
8b506c
+                }
8b506c
+            }
8b506c
+            if (ep->instname == NULL) /* no spaces found, so use the full name */
8b506c
+                ep->instname = strndup(ep->name, PROC_PID_STAT_CMD_MAXLEN);
8b506c
 	}
8b506c
+	
8b506c
+	/* mark pid as valid (new or still running) */
8b506c
+	ep->flags |= PROC_PID_FLAG_VALID;
8b506c
     }
8b506c
 
8b506c
     /* 
8b506c
-     * harvest exited pids from the pid hash table
8b506c
+     * harvest pids that have exit'ed
8b506c
      */
8b506c
+    numinst = 0;
8b506c
     for (i=0; i < proc_pid->pidhash.hsize; i++) {
8b506c
 	for (prev=NULL, node=proc_pid->pidhash.hash[i]; node != NULL;) {
8b506c
 	    next = node->next;
8b506c
 	    ep = (proc_pid_entry_t *)node->data;
8b506c
 	    // fprintf(stderr, "CHECKING key=%d node=" PRINTF_P_PFX "%p prev=" PRINTF_P_PFX "%p next=" PRINTF_P_PFX "%p ep=" PRINTF_P_PFX "%p valid=%d\n",
8b506c
 	    	// ep->id, node, prev, node->next, ep, ep->valid);
8b506c
-	    if (!(ep->flags & PROC_PID_FLAG_VALID)) {
8b506c
+	    if (ep->flags & PROC_PID_FLAG_VALID) {
8b506c
+		numinst++;
8b506c
+	    	prev = node;
8b506c
+	    }
8b506c
+	    else {
8b506c
+		// This process has exited.
8b506c
 	        //fprintf(stderr, "DELETED key=%d name=\"%s\"\n", ep->id, ep->name);
8b506c
+		if (ep->instname != NULL)
8b506c
+		    free(ep->instname);
8b506c
 		if (ep->name != NULL)
8b506c
 		    free(ep->name);
8b506c
 		if (ep->stat_buf != NULL)
8b506c
@@ -1017,13 +1006,25 @@ refresh_proc_pidlist(proc_pid_t *proc_pid, proc_pid_list_t *pids)
8b506c
 		free(ep);
8b506c
 		free(node);
8b506c
 	    }
8b506c
-	    else {
8b506c
-	    	prev = node;
8b506c
-	    }
8b506c
 	    if ((node = next) == NULL)
8b506c
 	    	break;
8b506c
 	}
8b506c
     }
8b506c
+
8b506c
+    /*
8b506c
+     * At this point, the hash table contains only valid pids. Refresh the indom table,
8b506c
+     * based on the updated process hash table. Indom table instance names are shared
8b506c
+     * with their hash table entry, do not free!
8b506c
+     */
8b506c
+    indomp->it_numinst = numinst;
8b506c
+    indomp->it_set = (pmdaInstid *)realloc(indomp->it_set, numinst * sizeof(pmdaInstid));
8b506c
+    for (idx=0, i=0; i < proc_pid->pidhash.hsize; i++) {
8b506c
+	for (node=proc_pid->pidhash.hash[i]; node != NULL; node=node->next, idx++) {
8b506c
+	    ep = (proc_pid_entry_t *)node->data;
8b506c
+	    indomp->it_set[idx].i_inst = ep->id; /* internal instid is pid */
8b506c
+	    indomp->it_set[idx].i_name = ep->instname; /* ptr ref, do not free */
8b506c
+	}
8b506c
+    }
8b506c
 }
8b506c
 
8b506c
 int
8b506c
diff --git a/src/pmdas/linux_proc/proc_pid.h b/src/pmdas/linux_proc/proc_pid.h
8b506c
index 344f4efc0..582965b3a 100644
8b506c
--- a/src/pmdas/linux_proc/proc_pid.h
8b506c
+++ b/src/pmdas/linux_proc/proc_pid.h
8b506c
@@ -20,6 +20,10 @@
8b506c
 #include "proc_runq.h"
8b506c
 #include "hotproc.h"
8b506c
 
8b506c
+/*
8b506c
+ * Maximim length of psargs and proc instance name.
8b506c
+ */
8b506c
+#define PROC_PID_STAT_CMD_MAXLEN	4096
8b506c
 
8b506c
 /*
8b506c
  * /proc/<pid>/stat metrics
8b506c
@@ -252,7 +256,8 @@ enum {
8b506c
 typedef struct {
8b506c
     int			id;	/* pid, hash key and internal instance id */
8b506c
     int			flags;	/* combinations of PROC_PID_FLAG_* values */
8b506c
-    char		*name;	/* external instance name (<pid> cmdline) */
8b506c
+    char		*name;	/* full command line and args */
8b506c
+    char		*instname; /* external instance name (truncated <pid> cmdline) */
8b506c
 
8b506c
     /* /proc/<pid>/stat cluster */
8b506c
     int			stat_buflen;