/*      cpufreq.cpp
//
//      Copyright 2005-2011 Lucas Tsatiris <systester.project@gmail.com>
//
//      This program is free software; you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation; either version 2 of the License, or
//      (at your option) any later version.
//
//      This program is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//      GNU General Public License for more details.
//
//      You should have received a copy of the GNU General Public License
//      along with this program; if not, write to the Free Software
//      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
//      MA 02110-1301, USA.
//
*/


/*
 *  Find what is the operational frequency of the cpu
 *
 *  ** Warning: Applying any optimization on this code may result a non-functional binary.
 *              Use your compiler with the -O0 optimization flag.
 */

#if !defined(CPUFREQ)
#define CPUFREQ

#if defined(__i386) || defined (__x86_64) || defined (__WIN32) || defined (__WIN64) || defined (_MSC_VER)
unsigned short dmifreq (void);
#endif /* __i386 ... _MSC_VER */

#if defined(__alpha__) && defined(__linux)	/* For Linux Alpha & GCC */
#define ASM_CODE(x) __asm__ __volatile__("rpcc %0" : "=r" ((x)) : : "memory")
#endif /* __alpha__ && __linux */

#if defined (__osf__)		/* For OSF1/Tru64 unix & Compaq C */
#include <c_asm.h>
#endif /* __osf__ */

#if defined(__i386) || defined(__x86_64)	/* For x86 and amd64 machines with GCC */
#if defined(__GNUC__) /* GCC */
#define ASM_CODE(x) __asm__ __volatile__ ("rdtsc" : "=A" ((x)) : : "memory")
#endif /* __GNUC__ */
#endif /* __i386 || __x86_64 */

#if defined(__hpux)		/* HPUX */
#include <sys/param.h>
#include <sys/pstat.h>
#endif /* __hpux */

#if (defined(__SUNPRO_CC) || defined(__SUNPRO_C)) && defined(__sun)	/* Solaris SunCC */
#include <sys/unistd.h>		/* for _SC_NPROCESSORS_CONF */
#include <sys/processor.h>	/* for processor_info_t */
#endif /* __SUNPRO_CC && __sun */

#if defined(_AIX) /* IBM AIX */
#include <sys/time.h>
#include <libperfstat.h>
#include <sys/dr.h>
#endif

#if defined (_MSC_VER) || defined (__MINGW32__) /* Visual Studio */
#include <windows.h>
#pragma intrinsic(__rdtsc)
#else
#include <sys/time.h>
#include <unistd.h>
#include <stdio.h>
#endif /* _MSC_VER */

#include <math.h>

#if (defined(__SUNPRO_CC) || defined(__SUNPRO_C)) && defined(__sun)	/* Solaris SunCC */
unsigned long long
cpufreq ()
{
  processor_info_t p;
  int i = -1, n, mhz = 0;
  n = sysconf (_SC_NPROCESSORS_CONF);

  do
    {
      i++;
      processor_info (i, &p);
    }
  while ((i < n) && (p.pi_state != P_ONLINE));
  return (unsigned long long) p.pi_clock;
}
#else

#if defined(__osf__) || defined (__linux) || defined (_MSC_VER) || defined (__APPLE__) || defined (__MINGW32__)
unsigned long long
real_time_clock (void)
{
  unsigned long long ret;

#if defined(__osf__) /* Tru64 unix */
  ret = asm ("rpcc %v0");

#elif defined (__linux)	|| defined (__APPLE__)	|| defined (__MINGW32__) /*  linux or darwin or windows with mingw*/
  ASM_CODE (ret);
#else /* Windows */
  ret = __rdtsc ();

#endif /* __osf__  */
  return ret;
}

unsigned long long
cpufreq ()
{
  unsigned long long result = 0;
#if defined (_MSC_VER) || defined (__MINGW32__) /* Windows */
  DWORD PriorityClass = GetPriorityClass (GetCurrentProcess ());
  DWORD ThreadPriority = GetThreadPriority (GetCurrentThread ());
  LARGE_INTEGER freq, tstart, tend;
  double tdiff;
  unsigned long long start, end;

  result = (unsigned long long) dmifreq ();
  if (result > 0)
    {
      Sleep (3000);
      return result;
    }

  SetThreadAffinityMask (GetCurrentThread (), 1);
  SetPriorityClass (GetCurrentProcess (), REALTIME_PRIORITY_CLASS);
  SetThreadPriority (GetCurrentThread (), THREAD_PRIORITY_TIME_CRITICAL);

  Sleep (1000);

  if (!QueryPerformanceFrequency (&freq))
    return 0;

  start = real_time_clock ();
  QueryPerformanceCounter (&tstart);
  Sleep (2000);
  end = real_time_clock ();
  QueryPerformanceCounter (&tend);

  tdiff = (double) ((tend.QuadPart - tstart.QuadPart) * 1e6 / freq.QuadPart);
  result = (unsigned long long) ((end - start) / tdiff);

  SetPriorityClass (GetCurrentProcess (), PriorityClass);
  SetThreadPriority (GetCurrentThread (), ThreadPriority);
  Sleep (1000);

#else
  struct timeval stv, etv;
  double elapsed;
  int start, end, microsecs = 100000;

#if defined(__osf__) /* Tru64 */
  microsecs = 200000;
#else /* Unices for x86/amd64 */
#if defined(__i386) || defined(__x86_64)
  result = (unsigned long long) dmifreq ();
  if (result > 0)
    {
#if defined (__MINGW32__)
      Sleep (3000);
#else
      sleep (3);
#endif  /* __MINGW32__ */
      return result;
    }
#endif /* __i386 || __x86_64 */
#endif /* __osf__  */

  start = real_time_clock ();
  gettimeofday (&stv, 0);
  usleep (microsecs);
  gettimeofday (&etv, 0);
  end = real_time_clock ();
  elapsed = (etv.tv_sec - stv.tv_sec) + (etv.tv_usec - stv.tv_usec) / 1e6;
  result = (unsigned long long) ((end - start) / elapsed / 1000000);
#endif /* _MSC_VER */
  return result;
}

#else
#if defined(__hpux) /* HPUX */
unsigned long long
cpufreq ()
{
  struct pst_processor pproc;
  unsigned int processor_clock, clk_tck = sysconf (_SC_CLK_TCK);
  pstat_getprocessor (&pproc, sizeof (pproc), 1, 0);
  processor_clock = (pproc.psp_iticksperclktick * clk_tck) / 1000000;
  return (unsigned long long) processor_clock;
}
#else
#if defined (_AIX) /* IBM AIX */
unsigned long long
cpufreq ()
{
  perfstat_cpu_total_t cpu;
  perfstat_cpu_total (NULL, &cpu, sizeof (perfstat_cpu_total_t), 1);
  return (unsigned long long) (cpu.processorHZ / 1000000);
}
#else
unsigned long long
cpufreq ()
{
  return 0;
}


#endif /* _AIX */
#endif /* _hpux */
#endif /* __osf__ || __linux ...*/
#endif /* __SUNPRO_CC || __SUNPRO_C ... */
#endif /* CPUFREQ */
