// $Id: childbirth_time.cpp 80826 2008-03-04 14:51:23Z wotte $

// ============================================================================
//
// = LIBRARY
//    (none)
//
// = FILENAME
//    childbirth_time.cpp
//
// = DESCRIPTION
//   This program is used to measure various child-creation mechanisms
//   on various platforms.  By default, the program measure the time
//   to 'fork' a new process using ACE_Process.spawn ().  Other tests
//   are possible as described below.   James Hu provides the idea to
//   batch measuring threads creation.
//
//   Usage: childbirth_time [-n ###] [-l ###] [-p|-f|-t|-a|-m|-x] [-h] [-e]
//
//          -n ###: Specify number of iteration in tens.  If this
//                  option is not specified, the default is
//                  MULTIPLY_FACTOR * (100 iterations,) which is
//                  equivalent to -n 10.
//
//          -l ###: Specify MULTIPLY_FACTOR.  Default is 10.
//
//             *-p: Measure the performance of forking a child process
//                  and exec an "empty" program.  This test uses
//                  ACE_Process.spawn ().  (Default)
//
//              -f: Measure the performance of native "fork" function
//                  call.  Notice that there is no equivalent NT
//                  function calls and this option is only available
//                  on UN*X platform.
//
//              -t: Measure the performance of native thread creation
//                  mechanisms.  On Solaris, this is thr_create ().
//                  On NT, this is CreateThread ().  Currently, only
//                  these two platforms are implemented.
//
//              -m: Measure the performance of Thread_Manager::spawn_n
//                  method.
//
//              -x: Test the baseline performance of ACE_Thread_Mutex.
//                  This really doesn't belong here
//
//              -a: Measure the performance of thread creation using
//                  ACE_OS::thr_create ().
//
//              -h: Use High Resolution Timer if supported by platform.
//
//              -e: Exec a program after fork ().  This option has no
//                  effect on NT.
//
//   = CREATION DATE
//      June 29, 1997
//
//   = AUTHOR
//      Nanbor Wang
//
// ============================================================================

// Process Creation profiling

#include "ace/OS_NS_unistd.h"
#include "ace/OS_main.h"
#include "ace/Get_Opt.h"
#include "ace/Process.h"
#include "ace/Profile_Timer.h"
#include "ace/Thread_Manager.h"

ACE_RCSID(Misc, childbirth_time, "$Id: childbirth_time.cpp 80826 2008-03-04 14:51:23Z wotte $")

#define  ACE_STOP_SIGN  ACE_OS::sleep (0)

#define MAX_NO_ITERATION  10000
#if defined (ACE_WIN32)
#define SUBPROGRAM ACE_TEXT ("date.exe")
#else
#define SUBPROGRAM ACE_TEXT ("date")
#endif

size_t MULTIPLY_FACTOR = 10;
typedef double (*Profiler)(size_t);
static int do_exec_after_fork = 0;

/// do nothing thread function
extern "C" void *ace_empty (void*)
{
  return 0;
}

static double
prof_ace_process (size_t iteration)
{
  if (iteration != 0)
    {
      ACE_Process_Options popt;
      ACE_Process aProcess;

      popt.command_line (SUBPROGRAM);

      iteration *= MULTIPLY_FACTOR;

      if (do_exec_after_fork == 0)
        popt.creation_flags (ACE_Process_Options::NO_EXEC);

      ACE_Profile_Timer ptimer;
      ACE_Profile_Timer::ACE_Elapsed_Time et;
      double time = 0;
      pid_t result;

      for (size_t c = 0; c < iteration; c++)
        {
          ACE_STOP_SIGN;
          ptimer.start ();
          result = aProcess.spawn (popt);
          ptimer.stop ();

          if (result == -1)
            ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "process.spawn"), -1);
          else if (do_exec_after_fork == 0 && result == 0)
            ACE_OS::exit (0) ;
          else
            {
              ptimer.elapsed_time (et);
              time += et.real_time;
            }
        }

      return time / iteration;
    }
  else
    return -1.0;
}

static double
prof_fork (size_t iteration)
{
#if !defined (ACE_LACKS_EXEC)
  if (iteration != 0)
    {
      ACE_Profile_Timer ptimer;
      ACE_Profile_Timer::ACE_Elapsed_Time et;
      double time = 0;

      iteration *= MULTIPLY_FACTOR;

      for (size_t i = 0; i < iteration; i++)
        {
          ACE_STOP_SIGN;
          ptimer.start ();
          switch (ACE_OS::fork ())
            {
            case -1:
              ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "process.spawn"), -1);
              /* NOTREACHED */
            case 0:
              ACE_OS::exit (0);
              /* NOTREACHED */
              break;
            default:
              ptimer.stop ();
              ptimer.elapsed_time (et);
              time += et.real_time;
            }
        }
      return time / iteration;
    }
  else
    return -1.0;
#else
  ACE_UNUSED_ARG (iteration);
  ACE_ERROR_RETURN ((LM_ERROR, "fork () is not supported on this platform."), -1);
#endif
}

static double
prof_native_thread (size_t iteration)
{
#if defined (ACE_HAS_THREADS)  && (defined (ACE_HAS_WTHREADS) || defined (ACE_HAS_STHREADS))
  if (iteration != 0)
    {
      ACE_Profile_Timer ptimer;
      ACE_Profile_Timer::ACE_Elapsed_Time et;
      double time = 0;

      for (size_t i = 0; i < iteration; i++)
        {
          ACE_STOP_SIGN;
          ptimer.start ();
          for (size_t j = 0; j < MULTIPLY_FACTOR; j++)
            {
#if defined (ACE_HAS_WTHREADS)
              if (::CreateThread (0,
                                  0,
                                  LPTHREAD_START_ROUTINE (ace_empty),
                                  0,
                                  CREATE_SUSPENDED,
                                  0) == 0)
#elif defined (ACE_HAS_STHREADS)
                //FUZZ: disable check_for_lack_ACE_OS
                if (::thr_create (0,
                                  0,
                                  &ace_empty,
                                  0,
                                  THR_SUSPENDED,
                                  0) != 0)
#endif
                  ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "CreateThread"), -1);
                //FUZZ: enable check_for_lack_ACE_OS
            }
          ptimer.stop ();
          ptimer.elapsed_time (et);
          time += et.real_time;
        }
      iteration *= MULTIPLY_FACTOR;
      return time / iteration;
    }
  else
    return -1.0;
#else
  ACE_UNUSED_ARG (iteration);
  ACE_ERROR_RETURN ((LM_ERROR, "Testing of native threads is not supported on this platform.\n"), -1);
#endif
}

static double
prof_ace_os_thread (size_t iteration)
{
#if defined (ACE_HAS_THREADS)
  if (iteration != 0)
    {
      ACE_Profile_Timer ptimer;
      ACE_Profile_Timer::ACE_Elapsed_Time et;
      double time = 0;

      for (size_t i = 0; i < iteration; i++)
        {
          ACE_STOP_SIGN;
          ptimer.start ();

          for (size_t j = 0; j < MULTIPLY_FACTOR; j++)
            if (ACE_OS::thr_create ((ACE_THR_FUNC) ace_empty,
                                    0,
                                    THR_SUSPENDED,
                                    0) == -1)
              ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "CreateThread"), -1);

          ptimer.stop ();
          ptimer.elapsed_time (et);
          time += et.real_time;
        }
      iteration *= MULTIPLY_FACTOR;
      return time / iteration;
    }
  else
    return -1.0;
#else
  ACE_UNUSED_ARG (iteration);
  ACE_ERROR_RETURN ((LM_ERROR, "Threads are not supported on this platform.\n"), -1);
#endif
}

static double
prof_tm_thread (size_t iteration)
{
#if defined (ACE_HAS_THREADS)
  if (iteration != 0)
    {
      ACE_Profile_Timer ptimer;
      ACE_Profile_Timer::ACE_Elapsed_Time et;
      double time = 0;

      for (size_t i = 0; i < iteration; i++)
        {
          ACE_STOP_SIGN;
          ptimer.start ();

          if (ACE_Thread_Manager::instance ()->spawn_n (MULTIPLY_FACTOR,
                                                        (ACE_THR_FUNC) ace_empty,
                                                        0,
                                                        THR_SUSPENDED) == -1)
            ACE_ERROR_RETURN ((LM_ERROR, "%p\n", "CreateThread"), -1);

          ptimer.stop ();
          ptimer.elapsed_time (et);
          time += et.real_time;
        }
      iteration *= MULTIPLY_FACTOR;
      return time / iteration;
    }
  else
    return -1.0;
#else
  ACE_UNUSED_ARG (iteration);
  ACE_ERROR_RETURN ((LM_ERROR, "Threads are not supported on this platform."), -1);
#endif
}

static double
prof_mutex_base (size_t iteration)
{
#if defined (ACE_HAS_THREADS)
  ACE_Thread_Mutex plain;
  if (iteration != 0)
    {
      ACE_Profile_Timer ptimer;
      ACE_Profile_Timer::ACE_Elapsed_Time et;
      double time = 0;

      for (size_t i = 0; i < iteration; i++)
        {
          ACE_STOP_SIGN;
          ptimer.start ();

          for (size_t j=0; j < MULTIPLY_FACTOR; j++)
            {
              plain.acquire ();
              plain.release ();
            }

          ptimer.stop ();
          ptimer.elapsed_time (et);
          time += et.real_time;
        }
      iteration *= MULTIPLY_FACTOR;
      return time / iteration;
    }
  else
    return -1.0;
#else
  ACE_UNUSED_ARG (iteration);
  ACE_ERROR_RETURN ((LM_ERROR, "Threads are not supported on this platform."), -1);
#endif
}

int
ACE_TMAIN (int argc, ACE_TCHAR* argv[])
{
  ACE_Get_Opt get_opt (argc, argv, ACE_TEXT("n:l:pftahmxe"));
  int c;
  size_t iteration = 10;
  Profiler profiler = 0;
  const char *profile_name = 0 ;

  while ((c=get_opt ()) != -1)
    {
      switch (c)
        {
        case 'n':
          iteration = ACE_OS::atoi (get_opt.opt_arg ());
          break;
        case 'l':
          MULTIPLY_FACTOR = static_cast<size_t> (ACE_OS::atoi (get_opt.opt_arg ()));
          break;
        case 'p':                       // test ACE_Process.spawn ()
          profiler = prof_ace_process;
          profile_name = "ACE_Process.spawn ()";
          break;
        case 'f':                       // test fork ()
          profiler = prof_fork;
          profile_name = "fork ()";
          break;
        case 't':                       // test native thread creation
          profiler = prof_native_thread;
          profile_name = "native threads";
          break;
        case 'a':                       // test ACE_OS::thr_create
          profiler = prof_ace_os_thread;
          profile_name = "ACE_OS::thr_create ()";
          break;
        case 'm':
          profiler = prof_tm_thread;
          profile_name = "ACE_Thread_Manager::spawn_n ()";
          break;
        case 'x':
          profiler = prof_mutex_base;
          profile_name = "ACE_Thread_Mutex Baseline";
          break;
        case 'h':                       // use high resolution timer
          ACE_High_Res_Timer::get_env_global_scale_factor ();
          break;
        case 'e':
          do_exec_after_fork = 1;
          break;
        default:
          break;
        }
    }

  if (profiler == 0)
    ACE_ERROR_RETURN ((LM_ERROR, "Usage: childbirth_time {-p|-f|-t|-a|-m|-x} [-n ###] [-L ###] [-h] [-e]\n"), 1);
  else
    {
      double time = profiler (iteration);
      if (time > 0)
        ACE_DEBUG ((LM_DEBUG,
                    "Average performance of %d iterations of %s: %.0f usec\n",
                    iteration * MULTIPLY_FACTOR, profile_name, time * 1e6));
    }
  return 0;
}
