/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ // vim:cindent:sw=4:et:ts=8: /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Netscape Communications Corp. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Jim Nance * L. David Baron - JP_REALTIME, JPROF_PTHREAD_HACK, and SIGUSR1 handling * Mike Shaver - JP_RTC_HZ support * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ // The linux glibc hides part of sigaction if _POSIX_SOURCE is defined #if defined(linux) #undef _POSIX_SOURCE #undef _SVID_SOURCE #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #endif #include #if defined(linux) #include #include #endif #include #include #include #include #include #include #include #include #include #include #include "libmalloc.h" #include "jprof.h" #include #include #include #ifdef NTO #include extern r_debug _r_debug; #else #include #endif static int gLogFD = -1; static pthread_t main_thread; static void startSignalCounter(unsigned long millisec); static int enableRTCSignals(bool enable); //---------------------------------------------------------------------- #if defined(i386) || defined(_i386) || defined(__x86_64__) static void CrawlStack(malloc_log_entry* me, void* stack_top, void* top_instr_ptr) { void **bp; #if defined(__i386) __asm__( "movl %%ebp, %0" : "=g"(bp)); #elif defined(__x86_64__) __asm__( "movq %%rbp, %0" : "=g"(bp)); #else // It would be nice if this worked uniformly, but at least on i386 and // x86_64, it stopped working with gcc 4.1, because it points to the // end of the saved registers instead of the start. bp = __builtin_frame_address(0); #endif u_long numpcs = 0; me->pcs[numpcs++] = (char*) top_instr_ptr; while (numpcs < MAX_STACK_CRAWL) { void** nextbp = (void**) *bp++; void* pc = *bp; if (nextbp < bp) { break; } if (bp > stack_top) { // Skip the signal handling. me->pcs[numpcs++] = (char*) pc; } bp = nextbp; } me->numpcs = numpcs; } #endif //---------------------------------------------------------------------- static int rtcHz; static int rtcFD = -1; #if defined(linux) || defined(NTO) static void DumpAddressMap() { // Turn off the timer so we don't get interrupts during shutdown #if defined(linux) if (rtcHz) { enableRTCSignals(false); } else #endif { startSignalCounter(0); } int mfd = open(M_MAPFILE, O_CREAT|O_WRONLY|O_TRUNC, 0666); if (mfd >= 0) { malloc_map_entry mme; link_map* map = _r_debug.r_map; while (NULL != map) { if (map->l_name && *map->l_name) { mme.nameLen = strlen(map->l_name); mme.address = map->l_addr; write(mfd, &mme, sizeof(mme)); write(mfd, map->l_name, mme.nameLen); #if 0 write(1, map->l_name, mme.nameLen); write(1, "\n", 1); #endif } map = map->l_next; } close(mfd); } } #endif static void EndProfilingHook(int signum) { DumpAddressMap(); puts("Jprof: profiling paused."); } //---------------------------------------------------------------------- static void Log(u_long aTime, void* stack_top, void* top_instr_ptr) { // Static is simply to make debugging tollerable static malloc_log_entry me; me.delTime = aTime; CrawlStack(&me, stack_top, top_instr_ptr); #ifndef NTO write(gLogFD, &me, offsetof(malloc_log_entry, pcs) + me.numpcs*sizeof(char*)); #else printf("Neutrino is missing the pcs member of malloc_log_entry!! \n"); #endif } static int realTime; /* Lets interrupt at 10 Hz. This is so my log files don't get too large. * This can be changed to a faster value latter. This timer is not * programmed to reset, even though it is capable of doing so. This is * to keep from getting interrupts from inside of the handler. */ static void startSignalCounter(unsigned long millisec) { struct itimerval tvalue; tvalue.it_interval.tv_sec = 0; tvalue.it_interval.tv_usec = 0; tvalue.it_value.tv_sec = millisec/1000; tvalue.it_value.tv_usec = (millisec%1000)*1000; if (realTime) { setitimer(ITIMER_REAL, &tvalue, NULL); } else { setitimer(ITIMER_PROF, &tvalue, NULL); } } static long timerMiliSec = 50; #if defined(linux) static int setupRTCSignals(int hz, struct sigaction *sap) { /* global */ rtcFD = open("/dev/rtc", O_RDONLY); if (rtcFD < 0) { perror("JPROF_RTC setup: open(\"/dev/rtc\", O_RDONLY)"); return 0; } if (sigaction(SIGIO, sap, NULL) == -1) { perror("JPROF_RTC setup: sigaction(SIGIO)"); return 0; } if (ioctl(rtcFD, RTC_IRQP_SET, hz) == -1) { perror("JPROF_RTC setup: ioctl(/dev/rtc, RTC_IRQP_SET, $JPROF_RTC_HZ)"); return 0; } if (ioctl(rtcFD, RTC_PIE_ON, 0) == -1) { perror("JPROF_RTC setup: ioctl(/dev/rtc, RTC_PIE_ON)"); return 0; } if (fcntl(rtcFD, F_SETSIG, 0) == -1) { perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETSIG, 0)"); return 0; } if (fcntl(rtcFD, F_SETOWN, getpid()) == -1) { perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETOWN, getpid())"); return 0; } return 1; } static int enableRTCSignals(bool enable) { static bool enabled = false; if (enabled == enable) { return 0; } enabled = enable; int flags = fcntl(rtcFD, F_GETFL); if (flags < 0) { perror("JPROF_RTC setup: fcntl(/dev/rtc, F_GETFL)"); return 0; } if (enable) { flags |= FASYNC; } else { flags &= ~FASYNC; } if (fcntl(rtcFD, F_SETFL, flags) == -1) { if (enable) { perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETFL, flags | FASYNC)"); } else { perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETFL, flags & ~FASYNC)"); } return 0; } return 1; } #endif static void StackHook( int signum, siginfo_t *info, void *ucontext) { static struct timeval tFirst; static int first=1; size_t millisec = 0; #if defined(linux) if (rtcHz && pthread_self() != main_thread) { // Only collect stack data on the main thread, for now. return; } #endif if(first && !(first=0)) { puts("Jprof: received first signal"); #if defined(linux) if (rtcHz) { enableRTCSignals(true); } else #endif { gettimeofday(&tFirst, 0); millisec = 0; } } else { #if defined(linux) if (rtcHz) { enableRTCSignals(true); } else #endif { struct timeval tNow; gettimeofday(&tNow, 0); double usec = 1e6*(tNow.tv_sec - tFirst.tv_sec); usec += (tNow.tv_usec - tFirst.tv_usec); millisec = static_cast(usec*1e-3); } } gregset_t &gregs = ((ucontext_t*)ucontext)->uc_mcontext.gregs; #ifdef __x86_64__ Log(millisec, (void*)gregs[REG_RSP], (void*)gregs[REG_RIP]); #else Log(millisec, (void*)gregs[REG_ESP], (void*)gregs[REG_EIP]); #endif if (!rtcHz) startSignalCounter(timerMiliSec); } NS_EXPORT_(void) setupProfilingStuff(void) { static int gFirstTime = 1; if(gFirstTime && !(gFirstTime=0)) { int startTimer = 1; int doNotStart = 1; int firstDelay = 0; int append = O_TRUNC; char *tst = getenv("JPROF_FLAGS"); /* Options from JPROF_FLAGS environment variable: * JP_DEFER -> Wait for a SIGPROF (or SIGALRM, if JP_REALTIME * is set) from userland before starting * to generate them internally * JP_START -> Install the signal handler * JP_PERIOD -> Time between profiler ticks * JP_FIRST -> Extra delay before starting * JP_REALTIME -> Take stack traces in intervals of real time * rather than time used by the process (and the * system for the process). This is useful for * finding time spent by the X server. * JP_APPEND -> Append to jprof-log rather than overwriting it. * This is somewhat risky since it depends on the * address map staying constant across multiple runs. */ if(tst) { if(strstr(tst, "JP_DEFER")) { doNotStart = 0; startTimer = 0; } if(strstr(tst, "JP_START")) doNotStart = 0; if(strstr(tst, "JP_REALTIME")) realTime = 1; if(strstr(tst, "JP_APPEND")) append = O_APPEND; char *delay = strstr(tst,"JP_PERIOD="); if(delay) { double tmp = strtod(delay+10, NULL); if(tmp>1e-3) { timerMiliSec = static_cast(1000 * tmp); } } char *first = strstr(tst, "JP_FIRST="); if(first) { firstDelay = atol(first+9); } char *rtc = strstr(tst, "JP_RTC_HZ="); if (rtc) { #if defined(linux) rtcHz = atol(rtc+10); timerMiliSec = 0; /* This makes JP_FIRST work right. */ realTime = 1; /* It's the _R_TC and all. ;) */ #define IS_POWER_OF_TWO(x) (((x) & ((x) - 1)) == 0) if (!IS_POWER_OF_TWO(rtcHz) || rtcHz < 2) { fprintf(stderr, "JP_RTC_HZ must be power of two and >= 2, " "but %d was provided; using default of 2048\n", rtcHz); rtcHz = 2048; } #else fputs("JP_RTC_HZ found, but RTC profiling only supported on " "Linux!\n", stderr); #endif } } if(!doNotStart) { if(gLogFD<0) { gLogFD = open(M_LOGFILE, O_CREAT | O_WRONLY | append, 0666); if(gLogFD<0) { fprintf(stderr, "Unable to create " M_LOGFILE); perror(":"); } else { struct sigaction action; sigset_t mset; // Dump out the address map when we terminate atexit(DumpAddressMap); main_thread = pthread_self(); sigemptyset(&mset); action.sa_handler = NULL; action.sa_sigaction = StackHook; action.sa_mask = mset; action.sa_flags = SA_RESTART | SA_SIGINFO; #if defined(linux) if (rtcHz) { if (!setupRTCSignals(rtcHz, &action)) { fputs("jprof: Error initializing RTC, NOT " "profiling\n", stderr); return; } } if (!rtcHz || firstDelay != 0) #endif if (realTime) { sigaction(SIGALRM, &action, NULL); } else { sigaction(SIGPROF, &action, NULL); } // make it so a SIGUSR1 will stop the profiling // Note: It currently does not close the logfile. // This could be configurable (so that it could // later be reopened). struct sigaction stop_action; stop_action.sa_handler = EndProfilingHook; stop_action.sa_mask = mset; stop_action.sa_flags = SA_RESTART; sigaction(SIGUSR1, &stop_action, NULL); printf("Jprof: Initialized signal handler and set " "timer for %lu %s, %d s " "initial delay\n", rtcHz ? rtcHz : timerMiliSec, rtcHz ? "Hz" : "ms", firstDelay); if(startTimer) { #if defined(linux) /* If we have an initial delay we can just use startSignalCounter to set up a timer to fire the first stackHook after that delay. When that happens we'll go and switch to RTC profiling. */ if (rtcHz && firstDelay == 0) { puts("Jprof: enabled RTC signals"); enableRTCSignals(true); } else #endif { puts("Jprof: started timer"); startSignalCounter(firstDelay*1000 + timerMiliSec); } } } } } } else { printf("setupProfilingStuff() called multiple times\n"); } }