/*
 * lat_usleep.c - usleep duration/latency
 *
 * The APIs for usleep(3), nanosleep(2), select(2), pselect(2),
 * getitimer(2) and setitimer(2) support resolutions down to 
 * a micro-second.  However, many implementations do not support 
 * such resolution.  Most current implementations (as of Fall 
 * 2002) simply put the process back on the run queue and the 
 * process may get run on the next scheduler time slice (10-20 
 * milli-second resolution).
 *
 * This benchmark measures the true latency from the timer/sleep
 * call to the resumption of program execution.  If the timers
 * actually worked properly, then the latency would be identical
 * to the requested duration, or a little longer, so the input
 * and output figures would be nearly identical.  In most current
 * implementations the value is rounded up to the next scheduler
 * timeslice (e.g., a resolution of 20 milli-seconds, with all
 * values rounded up).
 *
 * usage: lat_usleep [-u | -i] [-P <parallelism>] [-W <warmup>] \
 *		[-N <repetitions>] usecs
 *
 * Copyright (c) 2002 Carl Staelin.  Distributed under the FSF GPL with
 * additional restriction that results may published only if
 * (1) the benchmark is unmodified, and
 * (2) the version in the sccsid below is included in the report.
 * Support for this development by Sun Microsystems is gratefully acknowledged.
 */
char           *id = "$Id$\n";

#include "bench.h"
#include <sched.h>

typedef     enum {USLEEP, NANOSLEEP, SELECT, PSELECT, ITIMER} timer_e;

uint64          caught,
                n;
struct itimerval value;

typedef struct _state {
    unsigned long usecs;
} state_t;

void
bench_usleep(iter_t iterations, void *cookie)
{
    state_t        *state = (state_t*)cookie;

    while (iterations-- > 0) {
	usleep(state->usecs);
    }
}

void
bench_nanosleep(iter_t iterations, void *cookie)
{
    state_t        *state = (state_t*)cookie;
    struct timespec req;
    struct timespec rem;

    req.tv_sec = 0;
    req.tv_nsec = state->usecs * 1000;

    while (iterations-- > 0) {
	if (nanosleep(&req, &rem) < 0) {
	    while (nanosleep(&rem, &rem) < 0)
		;
	}
    }
}

void
bench_select(iter_t iterations, void *cookie)
{
    state_t        *state = (state_t*)cookie;
    struct timeval  tv;

    while (iterations-- > 0) {
	tv.tv_sec = 0;
	tv.tv_usec = state->usecs;
	select(0, NULL, NULL, NULL, &tv);
    }
}

#ifdef _POSIX_SELECT
void
bench_pselect(iter_t iterations, void *cookie)
{
    state_t        *state = (state_t*)cookie;
    struct timespec ts;

    while (iterations-- > 0) {
	ts.tv_sec = 0;
	ts.tv_nsec = state->usecs * 1000;
	pselect(0, NULL, NULL, NULL, &ts, NULL);
    }
}
#endif /* _POSIX_SELECT */

void
interval(int x)
{
    if (++caught == n) {
	caught = 0;
	n = benchmp_interval(benchmp_getstate());
    }

    setitimer(ITIMER_REAL, &value, NULL);
}

void
initialize(iter_t iterations, void *cookie)
{
    state_t        *state = (state_t*)cookie;
    struct sigaction sa;

    if (iterations) return;

    value.it_interval.tv_sec = 0;
    value.it_interval.tv_usec = state->usecs;
    value.it_value.tv_sec = 0;
    value.it_value.tv_usec = state->usecs;

    sa.sa_handler = interval;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGALRM, &sa, 0);
}

void
bench_itimer(iter_t iterations, void *cookie)
{
    n = iterations;
    caught = 0;

    /*
     * start the first timing interval 
     */
    start(0);

    /*
     * create the first timer, causing us to jump to interval() 
     */
    setitimer(ITIMER_REAL, &value, NULL);

    while (1) {
	sleep(100000);
    }
}

int
set_realtime()
{
    struct sched_param sp;

    sp.sched_priority = sched_get_priority_max(SCHED_RR);
    if (sched_setscheduler(0, SCHED_RR, &sp) >= 0) return TRUE;
    perror("sched_setscheduler");
    return FALSE;
}

int
main(int ac, char **av)
{
    int             realtime = 0;
    int		    parallel = 1;
    int             warmup = 0;
    int             repetitions = -1;
    int             c;
    char            buf[512];
    timer_e	    what = USLEEP;
    state_t         state;
    char           *scheduler = "";
    char           *mechanism = "usleep";
    char           *usage = "[-r] [-u <method>] [-P <parallelism>] [-W <warmup>] [-N <repetitions>] usecs\nmethod=usleep|nanosleep|select|pselect|itimer\n";

    realtime = 0;

    while ((c = getopt(ac, av, "ru:W:N:")) != EOF) {
	switch (c) {
	case 'r':
	    realtime = 1;
	    break;
	case 'u':
	    if (strcmp(optarg, "usleep") == 0) {
		what = USLEEP;
		mechanism = "usleep";
	    } else if (strcmp(optarg, "nanosleep") == 0) {
		what = NANOSLEEP;
		mechanism = "nanosleep";
	    } else if (strcmp(optarg, "select") == 0) {
		what = SELECT;
		mechanism = "select";
#ifdef _POSIX_SELECT
	    } else if (strcmp(optarg, "pselect") == 0) {
		what = PSELECT;
		mechanism = "pselect";
#endif /* _POSIX_SELECT */
	    } else if (strcmp(optarg, "itimer") == 0) {
		what = ITIMER;
		mechanism = "itimer";
	    } else {
		lmbench_usage(ac, av, usage);
	    }
	    break;
	case 'P':
	    parallel = atoi(optarg);
	    if (parallel <= 0) lmbench_usage(ac, av, usage);
	    break;
	case 'W':
	    warmup = atoi(optarg);
	    break;
	case 'N':
	    repetitions = atoi(optarg);
	    break;
	default:
	    lmbench_usage(ac, av, usage);
	    break;
	}
    }
    if (optind != ac - 1) {
	lmbench_usage(ac, av, usage);
    }

    state.usecs = bytes(av[optind]);
    if (realtime && set_realtime()) scheduler = "realtime ";

    switch (what) {
    case USLEEP:
	benchmp(NULL, bench_usleep, NULL, 
		0, parallel, warmup, repetitions, &state);
	break;
    case NANOSLEEP:
	benchmp(NULL, bench_nanosleep, NULL, 
		0, parallel, warmup, repetitions, &state);
	break;
    case SELECT:
	benchmp(NULL, bench_select, NULL, 
		0, parallel, warmup, repetitions, &state);
	break;
#ifdef _POSIX_SELECT
    case PSELECT:
	benchmp(NULL, bench_pselect, NULL, 
		0, parallel, warmup, repetitions, &state);
	break;
#endif /* _POSIX_SELECT */
    case ITIMER:
	benchmp(initialize, bench_itimer, NULL, 
		0, parallel, warmup, repetitions, &state);
	break;
    default:
	lmbench_usage(ac, av, usage);
	break;
    }
    sprintf(buf, "%s%s %lu microseconds", scheduler, mechanism, state.usecs);
    micro(buf, get_n());
    return (0);
}
