diff --git a/winsup/cygwin/cygheap.cc b/winsup/cygwin/cygheap.cc index 6493485c3..4932cf0b1 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 e379df1d2..02335eb69 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 96b1189ee..3baf93563 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,44 @@ _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; + /* 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 PROFDIR contains a null value, no profiling - output is produced */ - if (*profdir == '\0') { - return; - } + if GMON_OUT_PREFIX is specified with at least one character, the + file's name is computed as "$GMON_OUT_PREFIX.$pid". - limit = buf + sizeof buf - 1 - 10 - 1 - - strlen(__progname) - 1; - t = buf; - s = profdir; - while((*t = *s) != '\0' && t < limit) { - t++; - s++; - } - *t++ = '/'; + 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; + /* Covers positive pid_t values. */ + int32_t divisor = 1000*1000*1000; + pid_t pid = getpid(); + size_t len = strlen(prefix); - /* - * 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'; + if (len == 0) + len = strlen(prefix = filename); + buf = alloca(len + 13);// allow for '.', 10-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 0932ed94c..b0fb479e2 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 5b7da5897..8c7128c97 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 fad672881..6111b355a 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 #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 eb41c0844..2d7d6e394 100644 --- a/winsup/cygwin/profil.c +++ b/winsup/cygwin/profil.c @@ -18,9 +18,10 @@ #endif #include #include +#include #include #include -#include +#include #include "profil.h" #define SLEEPTIME (1000 / PROF_HZ) @@ -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; bail if anything amiss. */ + if (thr == INVALID_HANDLE_VALUE) + return; + pc = get_thrpc (thr); + if (pc == -1) + return; + + /* Code assumes there's only one profinfo in play: the static prof above. */ + 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 @@ -106,6 +124,7 @@ profile_off (struct profinfo *p) } if (p->targthr) CloseHandle (p->targthr); + p->targthr = 0; return 0; } @@ -121,6 +140,7 @@ profile_on (struct profinfo *p) GetCurrentProcess (), &p->targthr, 0, FALSE, DUPLICATE_SAME_ACCESS)) { + p->targthr = 0; errno = ESRCH; return -1; } @@ -129,7 +149,7 @@ profile_on (struct profinfo *p) if (!p->quitevt) { - CloseHandle (p->quitevt); + CloseHandle (p->targthr); p->targthr = 0; errno = EAGAIN; return -1; @@ -148,8 +168,8 @@ profile_on (struct profinfo *p) } /* Set profiler thread priority to highest to be sure that it gets the - processor as soon it request it (i.e. when the Sleep terminate) to get - the next data out of the profile. */ + processor as soon it requests it (i.e. when the Sleep terminates) to get + the next data sample as quickly as possible. */ SetThreadPriority (p->profthr, THREAD_PRIORITY_TIME_CRITICAL); @@ -157,16 +177,41 @@ profile_on (struct profinfo *p) } /* - * start or stop profiling + * Restart profiling in child after fork. * - * profiling goes into the SAMPLES buffer of size SIZE (which is treated - * as an array of u_shorts of size size/2) + * The profiling control info in prof needs to be selectively updated. + * Items counter, lowpc, highpc, and scale are correct as-is. But items + * targthr, profthr, and quitevt need updating because these copied HANDLE + * values are only valid in parent process. We also zero out the sample + * buffer so that samples aren't double-counted if multiple gmon.out files + * are aggregated. We calculate buffer's size from other data in prof. + */ +static void +profile_child (void) +{ + /* Bail if this was a fork after profiling turned off or was never on. */ + if (!prof.targthr) + return; + + /* Figure out how big the sample buffer is and zero it out. */ + size_t size = PROFIDX (prof.highpc, prof.lowpc, prof.scale) << 1; + memset ((char *) prof.counter, 0, size); + + /* Replace stale HANDLEs in prof and create profiling thread. */ + profile_on (&prof); +} + +/* + * Start or stop profiling. * - * each bin represents a range of pc addresses from OFFSET. The number + * Profiling data goes into the SAMPLES buffer of size SIZE (which is treated + * as an array of u_shorts of size SIZE/2). + * + * Each bin represents a range of pc addresses from OFFSET. The number * of pc addresses in a bin depends on SCALE. (A scale of 65536 maps * each bin to two addresses, A scale of 32768 maps each bin to 4 addresses, * a scale of 1 maps each bin to 128k addreses). Scale may be 1 - 65536, - * or zero to turn off profiling + * or zero to turn off profiling. */ int profile_ctl (struct profinfo * p, char *samples, size_t size, @@ -191,6 +236,9 @@ profile_ctl (struct profinfo * p, char *samples, size_t size, prof.highpc = PROFADDR (maxbin, offset, scale); prof.scale = scale; + /* Set up callback to restart profiling in child after fork. */ + pthread_atfork (NULL, NULL, profile_child); + return profile_on (p); } return 0;