/*
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 2002-2009. All Rights Reserved.
 * 
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 * 
 * 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.
 * 
 * %CopyrightEnd%
 */

#ifndef __BENCHMARK_H__
#define __BENCHMARK_H__

/* The define __BENCHMARK__ is the master switch to turn on and off
 * benchmarking. This will enable the benchmark-BIFs in hipe_bif1.c.
 * Documentation for the BIFs is in hipe_bif1.c, and that is where you
 * will find the information about how to accually get some data out
 * from these timers and counters.
 */
/* #define __BENCHMARK__ */

#ifdef __BENCHMARK__
/*
 * The defines below enables different parts of the benchmaring.
 * Counters and timers that are disabled, always report zero in
 * the BIFs.
 */

/* BM_TIMERS keeps track of the time spent in diferent parts of the
 * system. It only measures accual active time, not time spent in idle
 * mode. These timers requires hardware support.  For Linux, use the
 * package perfctr from user.it.uu.se/~mikpe/linux/perfctr. If this
 * package is not specified when configuring the system
 * (--with-perfctr=PATH), the Solaris hrtime_t will be used.
 * To add new timers look below.
 */
#define BM_TIMERS

/* BM_COUNTERS count all kinds of events that occurs in the system.
 * Among other things it counts the number of messages, then number of
 * garbage collections, the number of processes spawned etc.
 * To add new counters look below.
 */
#define BM_COUNTERS

/* BM_MESSAGE_SIZES keeps a log of the size of all messages sent in
 * the system. This introduce an overhead in time for the shared heap
 * system since all message sizes have to be calculated at send.
 */
/* #define BM_MESSAGE_SIZES */

/* BM_HEAP_SIZES goes through all processes at garbage collection time
 * to sum their allocated and used heap sizes. In anything else than a
 * shared heap system, this will cost.
 */
/* #define BM_HEAP_SIZES */

/* BM_STATISTICS saves an entry in the file BM_STATISTICS_FILE. This
 * is done for each erlang node at exit time.
 */
/* #define BM_STATISTICS */

#endif /* __BENCHMARK__ */


#ifdef BM_STATISTICS
#  define BM_STATISTICS_FILE "/tmp/erlang_statistics.joppe.log"
#endif /* BM_STATISTICS */


/************ There are no more settings below this line *************/

/*
 * Maintenance and how to add new stuff is documented by the code
 * below ;-)
 */

#ifdef BM_COUNTERS
/*********************************************************************
 * To add new counters:
 *
 * Add the variable here AND in benchmark.c. Use the macro
 * BM_COUNT(var) in the code where you want to increase it.
 *
 */
extern unsigned long long processes_busy;
extern unsigned long long processes_spawned;
extern unsigned long long messages_sent;
extern unsigned long long messages_copied;
extern unsigned long long messages_ego;
extern unsigned long long minor_gc;
extern unsigned long long major_gc;
#ifdef HYBRID
extern unsigned long long minor_global_gc;
extern unsigned long long major_global_gc;
extern unsigned long long gc_in_copy;
#ifdef INCREMENTAL
extern unsigned long long minor_gc_cycles;
extern unsigned long long major_gc_cycles;
extern unsigned long long minor_gc_stages;
extern unsigned long long major_gc_stages;
#endif
#endif

#define BM_COUNT(var) (var)++;

#define BM_EGO_COUNT(send,rec) {     \
    if ((send) == (rec))             \
        BM_COUNT(messages_ego);      }

#define BM_LAZY_COPY_START long long gcs = minor_global_gc + major_global_gc;
#define BM_LAZY_COPY_STOP { gcs = (minor_global_gc + major_global_gc) - gcs; \
    if (gcs > gc_in_copy) gc_in_copy = gcs; }

#else /* !BM_COUNTERS */
#  define BM_COUNT(var)
#  define BM_EGO_COUNT(send,rec)
#  define BM_LAZY_COPY_START
#  define BM_LAZY_COPY_STOP
#endif /* BM_COUNTERS */


#ifdef BM_TIMERS
/*********************************************************************
 * To add new timers:
 *
 * Add the variable below using the form extern BM_TIMER_T blah_time.
 * Also add them in benchmark.c using the macro NEW_TIMER(blah).  Use
 * the macro BM_SWAP_TIMER(from,blah) ... BM_SWAP_TIMER(blah,to) to
 * start and stop the new timer. Note, that you have to know what
 * timer is running at the place where you want to insert your new
 * timer to be able to stop and start (from,to) it.
 *
 * You can use the macros BM_STOP_TIMER(blah) and BM_START_TIMER(blah)
 * around code that should not be timed at all. As above, you have to
 * know what timer to start and stop. The system timer is running at
 * most places in the emulator. Only the garbage collector and the
 * message sending has its own timers at the moment.
 *
 * The timer_time used when stopping timers is the time it takes to
 * start and stop the timers, calculated in init_benchmarking(). If it
 * is not there, the time it takes to do this will accually be
 * substantial compared to some small times in the system we want to
 * meassure (send time in shared heap for instance).
 */

#if (defined(__i386__) || defined(__x86_64__)) && USE_PERFCTR
#include "libperfctr.h"

#define BM_TIMER_T double

extern struct vperfctr *system_clock;
extern double cpu_khz;
extern BM_TIMER_T start_time;

#define BM_START_TIMER(t) start_time =                                        \
                          (BM_TIMER_T)vperfctr_read_tsc(system_clock) /       \
                          cpu_khz;

#define BM_STOP_TIMER(t) do {                                                 \
    BM_TIMER_T tmp = ((BM_TIMER_T)vperfctr_read_tsc(system_clock) / cpu_khz); \
    tmp -= (start_time + timer_time);                                         \
    t##_time += (tmp > 0 ? tmp : 0);                                          \
} while(0)

#define BM_TIME_PRINTER(str,time) do {                                 \
    int min,sec,milli,micro;                                           \
    BM_TIMER_T tmp = (time) * 1000;                                    \
    micro = (uint)(tmp - ((int)(tmp / 1000)) * 1000);                  \
    tmp /= 1000;                                                       \
    milli = (uint)(tmp - ((int)(tmp / 1000)) * 1000);                  \
    tmp /= 1000;                                                       \
    sec = (uint)(tmp - ((int)(tmp / 60)) * 60);                        \
    min = (uint)tmp / 60;                                              \
    erts_fprintf(file,str": %d:%02d.%03d %03d\n",min,sec,milli,micro);  \
} while(0)

#else /* !USE_PERFCTR  (Assuming Solaris) */

#define BM_TIMER_T hrtime_t
#define BM_START_TIMER(t) system_clock = sys_gethrtime()
#define BM_STOP_TIMER(t) do {                                        \
    BM_TIMER_T tmp = (sys_gethrtime() - system_clock) - timer_time;  \
    t##_time += (tmp > 0 ? tmp : 0);                                 \
} while(0)

#define BM_TIME_PRINTER(str,time) do {                               \
    int min,sec,milli,micro;                                         \
    BM_TIMER_T tmp;                                                  \
    tmp = (time) / 1000;                                             \
    micro = tmp % 1000;                                              \
    tmp /= 1000;                                                     \
    milli = tmp % 1000;                                              \
    tmp /= 1000;                                                     \
    sec = tmp % 60;                                                  \
    min = tmp / 60;                                                  \
    erts_fprintf(file,str": %d:%02d.%03d %03d\n",min,sec,milli,micro);  \
} while(0)

extern BM_TIMER_T system_clock;
#endif /* USE_PERFCTR */

extern BM_TIMER_T timer_time;
extern BM_TIMER_T system_time;
extern BM_TIMER_T gc_time;
extern BM_TIMER_T minor_gc_time;
extern BM_TIMER_T major_gc_time;
extern BM_TIMER_T minor_global_gc_time;
extern BM_TIMER_T major_global_gc_time;
extern BM_TIMER_T send_time;
extern BM_TIMER_T copy_time;
extern BM_TIMER_T size_time;
extern BM_TIMER_T max_minor_time;
extern BM_TIMER_T max_major_time;
extern BM_TIMER_T max_global_minor_time;
extern BM_TIMER_T max_global_major_time;
extern BM_TIMER_T misc0_time;
extern BM_TIMER_T misc1_time;
extern BM_TIMER_T misc2_time;

#define MAX_PAUSE_TIME 500000
extern unsigned long local_pause_times[MAX_PAUSE_TIME];
extern unsigned long pause_times[MAX_PAUSE_TIME];
extern unsigned long pause_times_old[MAX_PAUSE_TIME];

#define MMU_INTERVAL 5  /* milli seconds */
extern BM_TIMER_T mmu_counter;
extern BM_TIMER_T mmu;

#define BM_NEW_TIMER(t) BM_TIMER_T t##_time = 0;
#define BM_RESET_TIMER(t) t##_time = 0;
#define BM_SWAP_TIMER(t1,t2) do { BM_STOP_TIMER(t1); BM_START_TIMER(t2); } while(0)
#define BM_MMU_INIT() do {                                              \
    BM_TIMER_T gc = gc_time;                                            \
    while (gc > 0) {                                                    \
        if (gc > MMU_INTERVAL) {                                        \
            gc -= MMU_INTERVAL - mmu_counter;                           \
            erts_printf("%d\n",(int)((mmu / MMU_INTERVAL) * 100));      \
            mmu_counter = 0; mmu = 0;                                   \
        } else {                                                        \
            mmu_counter += gc;                                          \
            if (mmu_counter >= MMU_INTERVAL) {                          \
                mmu_counter -= MMU_INTERVAL;                            \
                erts_printf("%d\n",(int)((mmu / MMU_INTERVAL) * 100));  \
                mmu = 0;                                                \
            }                                                           \
            gc = 0;                                                     \
        }                                                               \
    }                                                                   \
    BM_RESET_TIMER(system);                                             \
    BM_RESET_TIMER(send);                                               \
    BM_RESET_TIMER(copy);                                               \
    BM_RESET_TIMER(size);                                               \
} while(0)

#define BM_MMU_READ() do {                                              \
    BM_TIMER_T mut = system_time + send_time + copy_time + size_time;   \
    while (mut > 0) {                                                   \
        if (mut > MMU_INTERVAL) {                                       \
            BM_TIMER_T tmp = MMU_INTERVAL - mmu_counter;                \
            mmu += tmp; mut -= tmp;                                     \
            erts_printf("%d\n",(int)((mmu / MMU_INTERVAL) * 100));      \
            mmu_counter = 0; mmu = 0;                                   \
        } else {                                                        \
            mmu_counter += mut; mmu += mut;                             \
            if (mmu_counter >= MMU_INTERVAL) {                          \
                mmu_counter -= MMU_INTERVAL;                            \
                mmu -= mmu_counter;                                     \
                erts_printf("%d\n",(int)((mmu / MMU_INTERVAL) * 100));  \
                mmu = mmu_counter;                                      \
            }                                                           \
            mut = 0;                                                    \
        }                                                               \
    }                                                                   \
} while(0)

#else /* !BM_TIMERS */
#  define BM_NEW_TIMER(t)
#  define BM_START_TIMER(t)
#  define BM_STOP_TIMER(t)
#  define BM_RESET_TIMER(t)
#  define BM_SWAP_TIMER(t1,t2)
#  define BM_TIME_PRINTER(str,time)
#  define BM_MMU_INIT()
#  define BM_MMU_READ()
#endif /* BM_TIMERS */

#ifdef BM_HEAP_SIZES
extern unsigned long long max_used_heap;
extern unsigned long long max_allocated_heap;
extern unsigned long long max_used_global_heap;
extern unsigned long long max_allocated_global_heap;
#endif /* BM_HEAP_SIZES */

#ifdef BM_MESSAGE_SIZES
extern unsigned long long words_sent;
extern unsigned long long words_copied;
extern unsigned long long words_prealloc;
extern unsigned long long message_sizes[1000];

#define BM_MESSAGE_COPIED(size) { \
    words_copied += size;         \
    BM_COUNT(messages_copied);    }

#define BM_PREALLOC_DATA(size) { \
    words_prealloc += size;      }

#define BM_MESSAGE(mess,send,rec) {  \
    Uint msize = size_object(mess);  \
    words_sent += msize;             \
    if (msize < 1000)                \
        message_sizes[msize]++;      \
    else                             \
        message_sizes[999]++;        \
    BM_EGO_COUNT(send,rec);          \
    BM_COUNT(messages_sent);         }

#else /* !BM_MESSAGE_SIZES */

#define BM_MESSAGE_COPIED(size) BM_COUNT(messages_copied);
#define BM_PREALLOC_DATA(size)
#define BM_MESSAGE(mess,send,rec) {  \
    BM_EGO_COUNT(send,rec);          \
    BM_COUNT(messages_sent);         }

#endif /* BM_MESSAGE_SIZES */

void init_benchmarking(void);
void save_statistics(void);

#endif /* _BENCHMARK_H_ */
