text: /****************************************************************************
  *   PROJECT: Unix (pthread) heartbeat logic for Stack/Cog VM
  *   FILE:    sqUnixHeartbeat.c
  *   CONTENT: 
  *
  *   AUTHOR:  Eliot Miranda
  *   ADDRESS: 
  *   EMAIL:   eliot.miranda@gmail.com
  *   RCSID:   $Id$
  *
  *   NOTES: 
  *  Feb   1st, 2012, EEM refactored into three separate files.
  *  July 31st, 2008, EEM added heart-beat thread.
  *  Aug  20th, 2009, EEM added 64-bit microsecond clock support code
  *
  *****************************************************************************/
  
  #if ITIMER_HEARTBEAT
  # if VM_TICKER
  #   include "sqUnixITimerTickerHeartbeat.c"
  # else
  #   include "sqUnixITimerHeartbeat.c"
  # endif
  #else /* ITIMER_HEARTBEAT */
  
  #include "sq.h"
  #include "sqAssert.h"
  #include "sqMemoryFence.h"
  #include "sqSCCSVersion.h"
  #include <errno.h>
  #include <pthread.h>
  #include <stdio.h> /* for fprintf */
  #include <sys/types.h>
  #include <sys/time.h>
  #include "sqaio.h"
  
  #define SecondsFrom1901To1970      2177452800LL
  #define MicrosecondsFrom1901To1970 2177452800000000LL
  
  #define MicrosecondsPerSecond 1000000LL
  #define MillisecondsPerSecond 1000LL
  
  #define MicrosecondsPerMillisecond 1000LL
  
  static unsigned volatile long long utcMicrosecondClock;
  static unsigned volatile long long localMicrosecondClock;
  static unsigned volatile long millisecondClock; /* for the ioMSecs clock. */
  static unsigned long long utcStartMicroseconds; /* for the ioMSecs clock. */
  static long long vmGMTOffset = 0;
  static unsigned long long frequencyMeasureStart = 0;
  static unsigned long heartbeats;
  
  #define microToMilliseconds(usecs) ((((usecs) - utcStartMicroseconds) \
  									/ MicrosecondsPerMillisecond) \
  									& MillisecondClockMask)
  
  #define LOG_CLOCK 1
  
  #if LOG_CLOCK
  # define LOGSIZE 1024
  static unsigned long long useclog[LOGSIZE];
  static unsigned long mseclog[LOGSIZE];
  static int logClock = 0;
  static unsigned int ulogidx = (unsigned int)-1;
  static unsigned int mlogidx = (unsigned int)-1;
  # define logusecs(usecs) do { sqLowLevelMFence(); \
  							if (logClock) useclog[++ulogidx % LOGSIZE] = (usecs); \
  						} while (0)
  # define logmsecs(msecs) do { sqLowLevelMFence(); \
  							if (logClock) mseclog[++mlogidx % LOGSIZE] = (msecs); \
  						} while (0)
  void
  ioGetClockLogSizeUsecsIdxMsecsIdx(sqInt *runInNOutp, void **usecsp, sqInt *uip, void **msecsp, sqInt *mip)
  {
  	logClock = *runInNOutp;
  	sqLowLevelMFence();
  	*runInNOutp = LOGSIZE;
  	*usecsp = useclog;
  	*uip = ulogidx % LOGSIZE;
  	*msecsp = mseclog;
  	*mip = mlogidx % LOGSIZE;
  }
  #else /* LOG_CLOCK */
  # define logusecs(usecs) 0
  # define logmsecs(msecs) 0
  void
  ioGetClockLogSizeUsecsIdxMsecsIdx(sqInt *np, void **usecsp, sqInt *uip, void **msecsp, sqInt *mip)
  {
  	*np = *uip = *mip = 0;
  	*usecsp = *msecsp = 0;
  }
  #endif /* LOG_CLOCK */
  
  /* Compute the current VM time basis, the number of microseconds from 1901. */
  
  static unsigned long long
  currentUTCMicroseconds()
  {
  	struct timeval utcNow;
  
  	gettimeofday(&utcNow,0);
  	return ((utcNow.tv_sec * MicrosecondsPerSecond) + utcNow.tv_usec)
  			+ MicrosecondsFrom1901To1970;
  }
  
  /*
   * Update the utc and local microsecond clocks, and the millisecond clock.
   * Since this is invoked from interupt code, and since the clocks are 64-bit values
   * that are read concurrently by the VM, care must be taken to access these values
   * atomically on 32-bit systems.  If they are not accessed atomically there is a
   * possibility of fetching the two halves of the clock from different ticks which
   * would cause a jump in the clock of 2^32 microseconds (1 hr, 11 mins, 34 secs).
   *
   * Since an interrupt could occur between any two instructions the clock must be
   * read atomically as well as written atomically.  If possible this can be
   * implemented without locks using atomic 64-bit reads and writes.
   */
  
  #include "sqAtomicOps.h"
  
  static void
  updateMicrosecondClock()
  {
  	unsigned long long newUtcMicrosecondClock;
  	unsigned long long newLocalMicrosecondClock;
  
  	newUtcMicrosecondClock = currentUTCMicroseconds();
  
  	/* The native clock may go backwards, e.g. due to NTP adjustments, although
  	 * why it can't avoid small backward steps itself, I don't know.  Simply
  	 * ignore backward steps and wait until the clock catches up again.  Of
  	 * course this will cause problems if the clock is manually adjusted.  To
  	 * which the doctor says, "don't do that".
  	 */
  	if (!asserta(newUtcMicrosecondClock >= utcMicrosecondClock)) {
  		logusecs(0); /* if logging log a backward step as 0 */
  		return;
  	}
  	newLocalMicrosecondClock = newUtcMicrosecondClock + vmGMTOffset;
  
  	set64(utcMicrosecondClock,newUtcMicrosecondClock);
  	set64(localMicrosecondClock,newLocalMicrosecondClock);
  	millisecondClock = microToMilliseconds(newUtcMicrosecondClock);
  
  	logusecs(newUtcMicrosecondClock);
  	logmsecs(millisecondClock);
  }
  
  void
  ioUpdateVMTimezone()
  {
  	updateMicrosecondClock();
  #ifdef HAVE_TM_GMTOFF
  	time_t utctt;
  	utctt = (get64(utcMicrosecondClock) - MicrosecondsFrom1901To1970)
  				/ MicrosecondsPerSecond;
  	vmGMTOffset = localtime(&utctt)->tm_gmtoff * MicrosecondsPerSecond;
  #else
  # ifdef HAVE_TIMEZONE
    extern time_t timezone, altzone;
    extern int daylight;
    vmGMTOffset = -1 * (daylight ? altzone : timezone) * MicrosecondsPerSecond;
  # else
  #  error: cannot determine timezone correction
  # endif
  #endif
  }
  
  sqLong
  ioHighResClock(void)
  {
    /* return the value of the high performance counter */
    sqLong value = 0;
  
  #if defined(__GNUC__) && (defined(i386) || defined(__i386) || defined(__i386__))
      __asm__ __volatile__ ("rdtsc" : "=A"(value));
  #elif defined(__GNUC__) && (defined(x86_64) || defined(__x86_64) || defined (__x86_64__))
      __asm__ __volatile__ ("rdtsc\n\t"			// Returns the time in EDX:EAX.
  						"shl $32, %%rdx\n\t"	// Shift the upper bits left.
  						"or %%rdx, %0"			// 'Or' in the lower bits.
  						: "=a" (value)
  						: 
  						: "rdx");
  #elif defined(__arm__) && (defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_ISA_A64))
  	/* tpr - do nothing for now; needs input from eliot to decide further */
  	/* Tim, not sure I have input beyond:
  		Is there a 64-bit clock on ARM?  If so, access it here :-)
  	 */
  #else
  # error "no high res clock defined"
  #endif
    return value;
  }
  
  unsigned volatile long long
  ioUTCMicroseconds() { return get64(utcMicrosecondClock); }
  
  unsigned volatile long long
  ioLocalMicroseconds() { return get64(localMicrosecondClock); }
  
  sqInt
  ioLocalSecondsOffset() { return vmGMTOffset / MicrosecondsPerSecond; }
  
  /* This is an expensive interface for use by Smalltalk or vm profiling code that
   * wants the time now rather than as of the last heartbeat.
   */
  unsigned volatile long long
  ioUTCMicrosecondsNow() { return currentUTCMicroseconds(); }
  
  unsigned long long
  ioUTCStartMicroseconds() { return utcStartMicroseconds; }
  
  unsigned volatile long long
  ioLocalMicrosecondsNow() { return currentUTCMicroseconds() + vmGMTOffset; };
  
  /* ioMSecs answers the millisecondClock as of the last tick. */
  long
  ioMSecs() { return millisecondClock; }
  
  /* ioMicroMSecs answers the millisecondClock right now */
  long ioMicroMSecs(void) { return microToMilliseconds(currentUTCMicroseconds());}
  
  /* returns the local wall clock time */
  sqInt
  ioSeconds(void) { return get64(localMicrosecondClock) / MicrosecondsPerSecond; }
  
  sqInt
  ioSecondsNow(void) { return ioLocalMicrosecondsNow() / MicrosecondsPerSecond; }
  
  sqInt
  ioUTCSeconds(void) { return get64(utcMicrosecondClock) / MicrosecondsPerSecond; }
  
  sqInt
  ioUTCSecondsNow(void) { return currentUTCMicroseconds() / MicrosecondsPerSecond; }
  
  /*
   * On Mac OS X use the following.
   * On Unix use dpy->ioRelinquishProcessorForMicroseconds
   */
  #if macintoshSqueak
  sqInt
  ioRelinquishProcessorForMicroseconds(sqInt microSeconds)
  {
      long	realTimeToWait;
  	extern usqLong getNextWakeupUsecs();
  	usqLong nextWakeupUsecs = getNextWakeupUsecs();
  	usqLong utcNow = get64(utcMicrosecondClock);
  
      if (nextWakeupUsecs <= utcNow) {
  		/* if nextWakeupUsecs is non-zero the next wakeup time has already
  		 * passed and we should not wait.
  		 */
          if (nextWakeupUsecs != 0)
  			return 0;
  		realTimeToWait = microSeconds;
      }
      else {
          realTimeToWait = nextWakeupUsecs - utcNow;
  		if (realTimeToWait > microSeconds)
  			realTimeToWait = microSeconds;
  	}
  
  	aioSleepForUsecs(realTimeToWait);
  
  	return 0;
  }
  #endif /* !macintoshSqueak */
  
  void
  ioInitTime(void)
  {
  	ioUpdateVMTimezone(); /* does updateMicrosecondClock as a side-effect */
  	updateMicrosecondClock(); /* this can now compute localUTCMicroseconds */
  	utcStartMicroseconds = utcMicrosecondClock;
  }
  
  static void
  heartbeat()
  {
  	int saved_errno = errno;
  
  	updateMicrosecondClock();
  	if (get64(frequencyMeasureStart) == 0) {
  		set64(frequencyMeasureStart,utcMicrosecondClock);
  		heartbeats = 0;
  	}
  	else
  		heartbeats += 1;
  	checkHighPriorityTickees(utcMicrosecondClock);
  	forceInterruptCheckFromHeartbeat();
  
  	errno = saved_errno;
  }
  
  typedef enum { dead, condemned, nascent, quiescent, active } machine_state;
  
  #define UNDEFINED 0xBADF00D
  
  static int					stateMachinePolicy = UNDEFINED;
  static struct sched_param	stateMachinePriority;
  
  static volatile machine_state beatState = nascent;
  
  #if !defined(DEFAULT_BEAT_MS)
  # define DEFAULT_BEAT_MS 2
  #endif
  static int beatMilliseconds = DEFAULT_BEAT_MS;
  static struct timespec beatperiod = { 0, DEFAULT_BEAT_MS * 1000 * 1000 };
  
  static void *
  beatStateMachine(void *careLess)
  {
      int er;
  	if ((er = pthread_setschedparam(pthread_self(),
  									stateMachinePolicy,
  									&stateMachinePriority))) {
  		/* Linux pthreads as of 2009 does not support setting the priority of
  		 * threads other than with real-time scheduling policies.  But such
  		 * policies are only available to processes with superuser privileges.
  		 * Linux kernels >= 2.6.13 support different thread priorities, but
  		 * require a suitable /etc/security/limits.d/VMNAME.conf.
  		 */
  		extern char *revisionAsString();
  		errno = er;
  		perror("pthread_setschedparam failed");
  #if PharoVM
  # define VMNAME "pharo"
  #elif NewspeakVM
  # define VMNAME "nsvm"
  #else
  # define VMNAME "squeak"
  #endif
          fprintf(stderr, "This VM uses a separate heartbeat thread to update its internal clock\n");
          fprintf(stderr, "and handle events.  For best operation, this thread should run at a\n");
          fprintf(stderr, "higher priority, however the VM was unable to change the priority.  The\n");
          fprintf(stderr, "effect is that heavily loaded systems may experience some latency\n");
          fprintf(stderr, "issues.  If this occurs, please create the appropriate configuration\n");
          fprintf(stderr, "file in /etc/security/limits.d/ as shown below:\n\n");
          fprintf(stderr, "cat <<END | sudo tee /etc/security/limits.d/%s.conf\n", VMNAME);
          fprintf(stderr, "*      hard    rtprio  2\n");
          fprintf(stderr, "*      soft    rtprio  2\n");
          fprintf(stderr, "END\n");
          fprintf(stderr, "\nand report to the %s mailing list whether this improves behaviour.\n", VMNAME);
          fprintf(stderr, "\nYou will need to log out and log back in for the limits to take effect.\n");
          fprintf(stderr, "For more information please see\n");
          fprintf(stderr, "https://github.com/OpenSmalltalk/opensmalltalk-vm/releases/tag/r3732#linux\n");
          // exit(errno);
  		// The VM may have issues with clock jitter due to the heartbeat thread
  		// not running at elevated priority. An exit may be appropriate in some
  		// cases, but for most users the above warning is sufficient.
  		// exit(errno);
  	}
  	beatState = active;
  	while (beatState != condemned) {
  # define MINSLEEPNS 2000 /* don't bother sleeping for short times */
  		struct timespec naptime = beatperiod;
  
  		while (nanosleep(&naptime, &naptime) == -1
  			&& naptime.tv_sec >= 0 /* oversleeps can return tv_sec < 0 */
  			&& (naptime.tv_sec > 0 || naptime.tv_nsec > MINSLEEPNS)) /*repeat*/
  			if (errno != EINTR) {
  				perror("nanosleep");
  				exit(1);
  			}
  		heartbeat();
  	}
  	beatState = dead;
  	return 0;
  }
  
  void
  ioInitHeartbeat()
  {
  	int er;
  	struct timespec halfAMo;
  	pthread_t careLess;
  
  	/* First time through choose a policy and priority for the heartbeat thread,
  	 * and install ioInitHeartbeat via pthread_atfork to be run again in a forked
  	 * child, restarting the heartbeat in a forked child.
  	 */
  	if (stateMachinePolicy == UNDEFINED) {
  		if ((er = pthread_getschedparam(pthread_self(),
  										&stateMachinePolicy,
  										&stateMachinePriority))) {
  			errno = er;
  			perror("pthread_getschedparam failed");
  			exit(errno);
  		}
  		assert(stateMachinePolicy != UNDEFINED);
  		++stateMachinePriority.sched_priority;
  		/* If the priority isn't appropriate for the policy (typically
  		 * SCHED_OTHER) then change policy.
  		 */
  		if (sched_get_priority_max(stateMachinePolicy) < stateMachinePriority.sched_priority)
  			stateMachinePolicy = SCHED_FIFO;
  		pthread_atfork(0, /*prepare*/ 0, /*parent*/ ioInitHeartbeat /*child*/);
  	}
  	else /* subsequently (in the child) init beatState before creating thread */
  		beatState = nascent;
  
  	halfAMo.tv_sec  = 0;
  	halfAMo.tv_nsec = 1000 * 100;
  	if ((er= pthread_create(&careLess,
  							(const pthread_attr_t *)0,
  							beatStateMachine,
  							0))) {
  		errno = er;
  		perror("beat thread creation failed");
  		exit(errno);
  	}
  	while (beatState == nascent)
  		nanosleep(&halfAMo, 0);
  }
  
  void
  ioSetHeartbeatMilliseconds(int ms)
  {
  	beatMilliseconds = ms;
  	beatperiod.tv_sec = beatMilliseconds / 1000;
  	beatperiod.tv_nsec = (beatMilliseconds % 1000) * 1000 * 1000;
  }
  
  int
  ioHeartbeatMilliseconds() { return beatMilliseconds; }
  
  
  /* Answer the average heartbeats per second since the stats were last reset.
   */
  unsigned long
  ioHeartbeatFrequency(int resetStats)
  {
  	unsigned long duration = (ioUTCMicroseconds() - get64(frequencyMeasureStart))
  						/ MicrosecondsPerSecond;
  	unsigned long frequency = duration ? heartbeats / duration : 0;
  
  	if (resetStats) {
  		unsigned long long zero = 0;
  		set64(frequencyMeasureStart,zero);
  	}
  	return frequency;
  }
  #endif /* ITIMER_HEARTBEAT */
  