[PATCH] gprof profiling of multi-threaded Cygwin programs, ver 2

Mark Geisert mark@maxrnd.com
Sat Feb 20 08:16:00 GMT 2016


Version 2 incorporating review comments of version 1.

Change log relative to winsup/cygwin:

         * include/sys/cygwin.h: Add CW_CYGHEAP_PROFTHR_ALL.
         * cygheap.cc (cygheap_profthr_all): New C-callable function that
         runs cygheap's threadlist handing each pthread's thread handle in
         turn to profthr_byhandle().
         * external.cc (cygwin_internal): Add case CW_CYGHEAP_PROFTHR_ALL.
         * gmon.c (_mcleanup): Add support for multiple simultaneous
         gmon.out* files named via environment variable GMON_OUT_PREFIX.
         * gmon.h (struct gmonparam): Make state decl volatile.
         * mcount.c (_MCOUNT_DECL): Change stores into gmonparam.state to use
         Interlocked operations. Add #include "winsup.h", update commentary.
         * profil.c (profthr_byhandle): New function abstracting out the
         updating of profile counters based on a thread handle.
         (profthr_func): Update to call profthr_byhandle() to sample the main
         thread then call cygheap_profthr_all() indirectly through
         cygwin_internal(CW_CYGHEAP_PROFTHR_ALL) to sample all other threads.

Thanks,

..mark
-------------- next part --------------

---
 winsup/cygwin/cygheap.cc           | 12 ++++++
 winsup/cygwin/external.cc          | 11 ++++++
 winsup/cygwin/gmon.c               | 81 ++++++++++++++++----------------------
 winsup/cygwin/gmon.h               |  2 +-
 winsup/cygwin/include/sys/cygwin.h |  2 +
 winsup/cygwin/mcount.c             | 12 +++---
 winsup/cygwin/profil.c             | 38 +++++++++++++-----
 7 files changed, 94 insertions(+), 64 deletions(-)

diff --git a/winsup/cygwin/cygheap.cc b/winsup/cygwin/cygheap.cc
index 6493485..4932cf0 100644
--- a/winsup/cygwin/cygheap.cc
+++ b/winsup/cygwin/cygheap.cc
@@ -744,3 +744,15 @@ init_cygheap::find_tls (int sig, bool& issig_wait)
     WaitForSingleObject (t->mutex, INFINITE);
   return t;
 }
+
+/* Called from profil.c to sample all non-main thread PC values for profiling */
+extern "C" void
+cygheap_profthr_all (void (*profthr_byhandle) (HANDLE))
+{
+  for (uint32_t ix = 0; ix < nthreads; ix++)
+    {
+      _cygtls *tls = cygheap->threadlist[ix].thread;
+      if (tls->tid)
+	profthr_byhandle (tls->tid->win32_obj_id);
+    }
+}
diff --git a/winsup/cygwin/external.cc b/winsup/cygwin/external.cc
index e379df1..02335eb 100644
--- a/winsup/cygwin/external.cc
+++ b/winsup/cygwin/external.cc
@@ -702,6 +702,17 @@ cygwin_internal (cygwin_getinfo_types t, ...)
 	}
 	break;
 
+      case CW_CYGHEAP_PROFTHR_ALL:
+	{
+	  typedef void (*func_t) (HANDLE);
+	  extern void cygheap_profthr_all (func_t);
+
+	  func_t profthr_byhandle = va_arg(arg, func_t);
+	  cygheap_profthr_all (profthr_byhandle);
+	  res = 0;
+	}
+	break;
+
       default:
 	set_errno (ENOSYS);
     }
diff --git a/winsup/cygwin/gmon.c b/winsup/cygwin/gmon.c
index 96b1189..553f3b7 100644
--- a/winsup/cygwin/gmon.c
+++ b/winsup/cygwin/gmon.c
@@ -151,7 +151,6 @@ void _mcleanup (void);
 void
 _mcleanup(void)
 {
-	static char gmon_out[] = "gmon.out";
 	int fd;
 	int hz;
 	int fromindex;
@@ -161,7 +160,8 @@ _mcleanup(void)
 	struct rawarc rawarc;
 	struct gmonparam *p = &_gmonparam;
 	struct gmonhdr gmonhdr, *hdr;
-	const char *proffile;
+	char *filename = (char *) "gmon.out";
+	char *prefix;
 #ifdef DEBUG
 	int log, len;
 	char dbuf[200];
@@ -173,58 +173,43 @@ _mcleanup(void)
 	hz = PROF_HZ;
 	moncontrol(0);
 
-#ifdef nope
-	if ((profdir = getenv("PROFDIR")) != NULL) {
-		extern char *__progname;
-		char *s, *t, *limit;
-		pid_t pid;
-		long divisor;
-
-		/* If PROFDIR contains a null value, no profiling
-		   output is produced */
-		if (*profdir == '\0') {
-			return;
-		}
-
-		limit = buf + sizeof buf - 1 - 10 - 1 -
-		    strlen(__progname) - 1;
-		t = buf;
-		s = profdir;
-		while((*t = *s) != '\0' && t < limit) {
-			t++;
-			s++;
-		}
-		*t++ = '/';
-
-		/*
-		 * Copy and convert pid from a pid_t to a string.  For
-		 * best performance, divisor should be initialized to
-		 * the largest power of 10 less than PID_MAX.
-		 */
-		pid = getpid();
-		divisor=10000;
-		while (divisor > pid) divisor /= 10;	/* skip leading zeros */
-		do {
-			*t++ = (pid/divisor) + '0';
+	/* We copy an undocumented glibc feature: customizing the profiler's
+	   output file name somewhat, depending on the env var GMON_OUT_PREFIX.
+	   if GMON_OUT_PREFIX is unspecified, the file's name is "gmon.out".
+
+	   if GMON_OUT_PREFIX is specified with at least one character, the
+	   file's name is computed as "$GMON_OUT_PREFIX.$pid".
+
+	   if GMON_OUT_PREFIX is specified but contains no characters, the
+	   file's name is computed as "gmon.out.$pid".  Cygwin-specific.
+	*/
+	if ((prefix = getenv("GMON_OUT_PREFIX")) != NULL) {
+		char *buf;
+		long divisor = 100000;	// the power of 10 bigger than PID_MAX
+		pid_t pid = getpid();
+		size_t len = strlen(prefix);
+
+		if (len == 0)
+			len = strlen(prefix = filename);
+		buf = alloca(len + 8);	// allows for '.', 5-digit pid, NUL, +1
+
+		memcpy(buf, prefix, len);
+		buf[len++] = '.';
+
+		while (divisor > pid)	// skip leading zeroes
+			divisor /= 10;
+		do {			// convert pid to digits and store 'em
+			buf[len++] = (pid / divisor) + '0';
 			pid %= divisor;
 		} while (divisor /= 10);
-		*t++ = '.';
-
-		s = __progname;
-		while ((*t++ = *s++) != '\0')
-			;
 
-		proffile = buf;
-	} else {
-		proffile = gmon_out;
+		buf[len] = '\0';
+		filename = buf;
 	}
-#else
-	proffile = gmon_out;
-#endif
 
-	fd = open(proffile , O_CREAT|O_TRUNC|O_WRONLY|O_BINARY, 0666);
+	fd = open(filename, O_CREAT|O_TRUNC|O_WRONLY|O_BINARY, 0666);
 	if (fd < 0) {
-		perror( proffile );
+		perror(filename);
 		return;
 	}
 #ifdef DEBUG
diff --git a/winsup/cygwin/gmon.h b/winsup/cygwin/gmon.h
index 0932ed9..b0fb479 100644
--- a/winsup/cygwin/gmon.h
+++ b/winsup/cygwin/gmon.h
@@ -153,7 +153,7 @@ struct rawarc {
  * The profiling data structures are housed in this structure.
  */
 struct gmonparam {
-	int		state;
+	volatile int	state;
 	u_short		*kcount;
 	size_t		kcountsize;
 	u_short		*froms;
diff --git a/winsup/cygwin/include/sys/cygwin.h b/winsup/cygwin/include/sys/cygwin.h
index 5b7da58..8c7128c 100644
--- a/winsup/cygwin/include/sys/cygwin.h
+++ b/winsup/cygwin/include/sys/cygwin.h
@@ -160,6 +160,7 @@ typedef enum
     CW_GETNSS_PWD_SRC,
     CW_GETNSS_GRP_SRC,
     CW_EXCEPTION_RECORD_FROM_SIGINFO_T,
+    CW_CYGHEAP_PROFTHR_ALL,
   } cygwin_getinfo_types;
 
 #define CW_LOCK_PINFO CW_LOCK_PINFO
@@ -221,6 +222,7 @@ typedef enum
 #define CW_GETNSS_PWD_SRC CW_GETNSS_PWD_SRC
 #define CW_GETNSS_GRP_SRC CW_GETNSS_GRP_SRC
 #define CW_EXCEPTION_RECORD_FROM_SIGINFO_T CW_EXCEPTION_RECORD_FROM_SIGINFO_T
+#define CW_CYGHEAP_PROFTHR_ALL CW_CYGHEAP_PROFTHR_ALL
 
 /* Token type for CW_SET_EXTERNAL_TOKEN */
 enum
diff --git a/winsup/cygwin/mcount.c b/winsup/cygwin/mcount.c
index fad6728..6111b35 100644
--- a/winsup/cygwin/mcount.c
+++ b/winsup/cygwin/mcount.c
@@ -41,6 +41,7 @@ static char rcsid[] = "$OpenBSD: mcount.c,v 1.6 1997/07/23 21:11:27 kstailey Exp
 #endif
 #include <sys/types.h>
 #include "gmon.h"
+#include "winsup.h"
 
 /*
  * mcount is called on entry to each function compiled with the profiling
@@ -70,11 +71,12 @@ _MCOUNT_DECL (size_t frompc, size_t selfpc)
 	p = &_gmonparam;
 	/*
 	 * check that we are profiling
-	 * and that we aren't recursively invoked.
+	 * and that we aren't recursively invoked by this thread
+	 * or entered anew by any other thread.
 	 */
-	if (p->state != GMON_PROF_ON)
+	if (InterlockedCompareExchange (
+		    &p->state, GMON_PROF_BUSY, GMON_PROF_ON) != GMON_PROF_ON)
 		return;
-	p->state = GMON_PROF_BUSY;
 	/*
 	 * check that frompcindex is a reasonable pc value.
 	 * for example:	signal catchers get called from the stack,
@@ -162,10 +164,10 @@ _MCOUNT_DECL (size_t frompc, size_t selfpc)
 		}
 	}
 done:
-	p->state = GMON_PROF_ON;
+	InterlockedExchange (&p->state, GMON_PROF_ON);
 	return;
 overflow:
-	p->state = GMON_PROF_ERROR;
+	InterlockedExchange (&p->state, GMON_PROF_ERROR);
 	return;
 }
 
diff --git a/winsup/cygwin/profil.c b/winsup/cygwin/profil.c
index eb41c08..9f183af 100644
--- a/winsup/cygwin/profil.c
+++ b/winsup/cygwin/profil.c
@@ -18,6 +18,7 @@
 #endif
 #include <windows.h>
 #include <stdio.h>
+#include <sys/cygwin.h>
 #include <sys/types.h>
 #include <errno.h>
 #include <math.h>
@@ -65,25 +66,42 @@ print_prof (struct profinfo *p)
 }
 #endif
 
-/* Everytime we wake up use the main thread pc to hash into the cell in the
-   profile buffer ARG. */
+/* Every time we wake up sample the main thread's pc to hash into the cell
+   in the profile buffer ARG.  Then all other pthreads' pc's are sampled.  */
 
-static void CALLBACK profthr_func (LPVOID);
+static void
+profthr_byhandle (HANDLE thr)
+{
+  size_t pc;
+
+  // sample the pc of the thread indicated by thr, but bail if anything amiss
+  if (thr == INVALID_HANDLE_VALUE)
+    return;
+  pc = get_thrpc (thr);
+  if (pc == -1)
+    return;
+
+  // code assumes there is only one profinfo in play: the static prof up top
+  if (pc >= prof.lowpc && pc < prof.highpc)
+    {
+      size_t idx = PROFIDX (pc, prof.lowpc, prof.scale);
+      prof.counter[idx]++;
+    }
+}
 
 static void CALLBACK
 profthr_func (LPVOID arg)
 {
   struct profinfo *p = (struct profinfo *) arg;
-  size_t pc, idx;
 
   for (;;)
     {
-      pc = (size_t) get_thrpc (p->targthr);
-      if (pc >= p->lowpc && pc < p->highpc)
-	{
-	  idx = PROFIDX (pc, p->lowpc, p->scale);
-	  p->counter[idx]++;
-	}
+      // record profiling sample for main thread
+      profthr_byhandle (p->targthr);
+
+      // record profiling samples for other pthreads, if any
+      cygwin_internal (CW_CYGHEAP_PROFTHR_ALL, profthr_byhandle);
+
 #if 0
       print_prof (p);
 #endif
-- 
2.7.0



More information about the Cygwin-patches mailing list