#define _GNU_SOURCE
#define _FILE_OFFSET_BITS 64
#define INTERBENCH_VERSION "0.30"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <strings.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sched.h>
#include <time.h>
#include <errno.h>
#include <semaphore.h>
#include <pthread.h>
#include <math.h>
#include <fenv.h>
#include <signal.h>
#include <sys/utsname.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/vmmeter.h>
#include "interbench.h"
#define MAX_UNAME_LENGTH 100
#define MAX_LOG_LENGTH ((MAX_UNAME_LENGTH) + 4)
#define MIN_BLK_SIZE 1024
#define DEFAULT_RESERVE 64
#define MB (1024 * 1024)
#define KB 1024
#define MAX_MEM_IN_MB (1024 * 64)
struct user_data {
unsigned long loops_per_ms;
unsigned long ram, swap;
int duration;
int do_rt;
int bench_nice;
int load_nice;
unsigned long custom_run;
unsigned long custom_interval;
unsigned long cpu_load;
char logfilename[MAX_LOG_LENGTH];
int log;
char unamer[MAX_UNAME_LENGTH];
char datestamp[13];
FILE *logfile;
} ud = {
.duration = 30,
.cpu_load = 4,
.log = 1,
};
static int m2l[2], l2m[2], m2b[2], b2m[2];
#define NOT_BENCHING (THREADS)
#define CUSTOM (THREADS - 1)
void emulate_none(struct thread *th);
void emulate_audio(struct thread *th);
void emulate_video(struct thread *th);
void emulate_x(struct thread *th);
void emulate_game(struct thread *th);
void emulate_burn(struct thread *th);
void emulate_write(struct thread *th);
void emulate_read(struct thread *th);
void emulate_ring(struct thread *th);
void emulate_compile(struct thread *th);
void emulate_memload(struct thread *th);
void emulate_hackbench(struct thread *th);
void emulate_custom(struct thread *th);
struct thread threadlist[THREADS] = {
{.label = "None", .name = emulate_none, .load = 1, .rtload = 1},
{.label = "Audio", .name = emulate_audio, .bench = 1, .rtbench = 1},
{.label = "Video", .name = emulate_video, .bench = 1, .rtbench = 1, .load = 1, .rtload = 1},
{.label = "X", .name = emulate_x, .bench = 1, .load = 1, .rtload = 1},
{.label = "Gaming", .name = emulate_game, .nodeadlines = 1, .bench = 1},
{.label = "Burn", .name = emulate_burn, .load = 1, .rtload = 1},
{.label = "Write", .name = emulate_write, .load = 1, .rtload = 1},
{.label = "Read", .name = emulate_read, .load = 1, .rtload = 1},
{.label = "Ring", .name = emulate_ring, .load = 0, .rtload = 0},
{.label = "Compile", .name = emulate_compile, .load = 1, .rtload = 1},
{.label = "Memload", .name = emulate_memload, .load = 1, .rtload = 1},
{.label = "Hack", .name = emulate_hackbench, .load = 0, .rtload = 0},
{.label = "Custom", .name = emulate_custom},
};
void init_sem(sem_t *sem);
void init_all_sems(struct sems *s);
void initialise_thread(int i);
void start_thread(struct thread *th);
void stop_thread(struct thread *th);
void terminal_error(const char *name)
{
fprintf(stderr, "\n");
perror(name);
exit (1);
}
void terminal_fileopen_error(FILE *fp, char *name)
{
if (fclose(fp) == -1)
terminal_error("fclose");
terminal_error(name);
}
unsigned long long get_nsecs(struct timespec *myts)
{
if (clock_gettime(CLOCK_REALTIME, myts))
terminal_error("clock_gettime");
return (myts->tv_sec * 1000000000 + myts->tv_nsec );
}
unsigned long get_usecs(struct timespec *myts)
{
if (clock_gettime(CLOCK_REALTIME, myts))
terminal_error("clock_gettime");
return (myts->tv_sec * 1000000 + myts->tv_nsec / 1000 );
}
void set_fifo(int prio)
{
struct sched_param sp;
memset(&sp, 0, sizeof(sp));
sp.sched_priority = prio;
if (sched_setscheduler(0, SCHED_FIFO, &sp) == -1) {
if (errno != EPERM)
terminal_error("sched_setscheduler");
}
}
void set_mlock(void)
{
int mlockflags;
mlockflags = MCL_CURRENT | MCL_FUTURE;
#if 0
mlockall(mlockflags);
#endif
}
void set_munlock(void)
{
#if 0
if (munlockall() == -1)
terminal_error("munlockall");
#endif
}
void set_thread_fifo(pthread_t pthread, int prio)
{
struct sched_param sp;
memset(&sp, 0, sizeof(sp));
sp.sched_priority = prio;
if (pthread_setschedparam(pthread, SCHED_FIFO, &sp) == -1)
terminal_error("pthread_setschedparam");
}
void set_normal(void)
{
struct sched_param sp;
memset(&sp, 0, sizeof(sp));
sp.sched_priority = 0;
if (sched_setscheduler(0, SCHED_OTHER, &sp) == -1) {
fprintf(stderr, "Weird, could not unset RT scheduling!\n");
}
}
void set_nice(int prio)
{
if (setpriority(PRIO_PROCESS, 0, prio) == -1)
terminal_error("setpriority");
}
int test_fifo(void)
{
struct sched_param sp;
memset(&sp, 0, sizeof(sp));
sp.sched_priority = 99;
if (sched_setscheduler(0, SCHED_FIFO, &sp) == -1) {
if (errno != EPERM)
terminal_error("sched_setscheduler");
goto out_fail;
}
if (sched_getscheduler(0) != SCHED_FIFO)
goto out_fail;
set_normal();
return 1;
out_fail:
set_normal();
return 0;
}
void set_thread_normal(pthread_t pthread)
{
struct sched_param sp;
memset(&sp, 0, sizeof(sp));
sp.sched_priority = 0;
if (pthread_setschedparam(pthread, SCHED_OTHER, &sp) == -1)
terminal_error("pthread_setschedparam");
}
void sync_flush(void)
{
if ((fflush(NULL)) == EOF)
terminal_error("fflush");
sync();
sync();
sync();
}
unsigned long compute_allocable_mem(void)
{
unsigned long total = ud.ram + ud.swap;
unsigned long usage = ud.ram * 110 / 100 ;
if (total - DEFAULT_RESERVE < usage)
usage = total - DEFAULT_RESERVE;
usage /= 1024;
if (usage > 2930)
usage = 2930;
return usage;
}
void burn_loops(unsigned long loops)
{
unsigned long i;
for (i = 0 ; i < loops ; i++)
asm volatile("" : : : "memory");
}
void burn_usecs(unsigned long usecs)
{
unsigned long ms_loops;
ms_loops = ud.loops_per_ms / 1000 * usecs;
burn_loops(ms_loops);
}
void microsleep(unsigned long long usecs)
{
struct timespec req, rem;
rem.tv_sec = rem.tv_nsec = 0;
req.tv_sec = usecs / 1000000;
req.tv_nsec = (usecs - (req.tv_sec * 1000000)) * 1000;
continue_sleep:
if ((nanosleep(&req, &rem)) == -1) {
if (errno == EINTR) {
if (rem.tv_sec || rem.tv_nsec) {
req.tv_sec = rem.tv_sec;
req.tv_nsec = rem.tv_nsec;
goto continue_sleep;
}
goto out;
}
terminal_error("nanosleep");
}
out:
return;
}
inline void post_sem(sem_t *s)
{
retry:
if ((sem_post(s)) == -1) {
if (errno == EINTR)
goto retry;
terminal_error("sem_post");
}
}
inline void wait_sem(sem_t *s)
{
retry:
if ((sem_wait(s)) == -1) {
if (errno == EINTR)
goto retry;
terminal_error("sem_wait");
}
}
inline int trywait_sem(sem_t *s)
{
int ret;
retry:
if ((ret = sem_trywait(s)) == -1) {
if (errno == EINTR)
goto retry;
if (errno != EAGAIN)
terminal_error("sem_trywait");
}
return ret;
}
inline ssize_t Read(int fd, void *buf, size_t count)
{
ssize_t retval;
retry:
retval = read(fd, buf, count);
if (retval == -1) {
if (errno == EINTR)
goto retry;
terminal_error("read");
}
return retval;
}
inline ssize_t Write(int fd, const void *buf, size_t count)
{
ssize_t retval;
retry:
retval = write(fd, &buf, count);
if (retval == -1) {
if (errno == EINTR)
goto retry;
terminal_error("write");
}
return retval;
}
unsigned long periodic_schedule(struct thread *th, unsigned long run_usecs,
unsigned long interval_usecs, unsigned long long deadline)
{
unsigned long long latency, missed_latency;
unsigned long long current_time;
struct tk_thread *tk;
struct data_table *tb;
struct timespec myts;
latency = 0;
tb = th->dt;
tk = &th->tkthread;
current_time = get_usecs(&myts);
if (current_time > deadline + tk->slept_interval)
latency = current_time - deadline- tk->slept_interval;
missed_latency = 0;
current_time = get_usecs(&myts);
if (interval_usecs && current_time > deadline + interval_usecs) {
unsigned long intervals;
deadline += interval_usecs;
intervals = (current_time - deadline) /
interval_usecs + 1;
tb->missed_deadlines += intervals;
missed_latency = intervals * interval_usecs;
deadline += intervals * interval_usecs;
tb->missed_burns += intervals;
goto bypass_burn;
}
burn_usecs(run_usecs);
current_time = get_usecs(&myts);
tb->achieved_burns++;
deadline += interval_usecs;
if (deadline >= current_time) {
tb->deadlines_met++;
} else {
if (interval_usecs) {
unsigned long intervals = (current_time - deadline) /
interval_usecs + 1;
tb->missed_deadlines += intervals;
missed_latency = intervals * interval_usecs;
deadline += intervals * interval_usecs;
if (intervals > 1)
tb->missed_burns += intervals;
} else {
deadline = current_time;
goto out_nosleep;
}
}
bypass_burn:
tk->sleep_interval = deadline - current_time;
post_sem(&tk->sem.start);
wait_sem(&tk->sem.complete);
out_nosleep:
latency += missed_latency;
if (latency > tb->max_latency)
tb->max_latency = latency;
tb->total_latency += latency;
tb->sum_latency_squared += latency * latency;
tb->nr_samples++;
return deadline;
}
void initialise_thread_data(struct data_table *tb)
{
tb->max_latency =
tb->total_latency =
tb->sum_latency_squared =
tb->deadlines_met =
tb->missed_deadlines =
tb->missed_burns =
tb->nr_samples = 0;
}
void create_pthread(pthread_t * thread, pthread_attr_t * attr,
void * (*start_routine)(void *), void *arg)
{
if (pthread_create(thread, attr, start_routine, arg))
terminal_error("pthread_create");
}
void join_pthread(pthread_t th, void **thread_return)
{
if (pthread_join(th, thread_return))
terminal_error("pthread_join");
}
void emulate_none(struct thread *th)
{
sem_t *s = &th->sem.stop;
wait_sem(s);
}
#define AUDIO_INTERVAL (50000)
#define AUDIO_RUN (AUDIO_INTERVAL / 20)
void emulate_audio(struct thread *th)
{
unsigned long long deadline;
sem_t *s = &th->sem.stop;
struct timespec myts;
th->decasecond_deadlines = 1000000 / AUDIO_INTERVAL * 10;
deadline = get_usecs(&myts);
while (1) {
deadline = periodic_schedule(th, AUDIO_RUN, AUDIO_INTERVAL,
deadline);
if (!trywait_sem(s))
return;
}
}
#define VIDEO_INTERVAL (1000000 / 60)
#define VIDEO_RUN (VIDEO_INTERVAL * 40 / 100)
void emulate_video(struct thread *th)
{
unsigned long long deadline;
sem_t *s = &th->sem.stop;
struct timespec myts;
th->decasecond_deadlines = 1000000 / VIDEO_INTERVAL * 10;
deadline = get_usecs(&myts);
while (1) {
deadline = periodic_schedule(th, VIDEO_RUN, VIDEO_INTERVAL,
deadline);
if (!trywait_sem(s))
return;
}
}
void emulate_x(struct thread *th)
{
unsigned long long deadline;
sem_t *s = &th->sem.stop;
struct timespec myts;
th->decasecond_deadlines = 100;
deadline = get_usecs(&myts);
while (1) {
int i, j;
for (i = 0 ; i <= 100 ; i++) {
j = 100 - i;
deadline = periodic_schedule(th, i * 1000, j * 1000,
deadline);
deadline += i * 1000;
if (!trywait_sem(s))
return;
}
}
}
#define GAME_INTERVAL (100000)
#define GAME_RUN (GAME_INTERVAL)
void emulate_game(struct thread *th)
{
unsigned long long deadline, current_time, latency;
sem_t *s = &th->sem.stop;
struct timespec myts;
struct data_table *tb;
tb = th->dt;
th->decasecond_deadlines = 1000000 / GAME_INTERVAL * 10;
while (1) {
deadline = get_usecs(&myts) + GAME_INTERVAL;
burn_usecs(GAME_RUN);
current_time = get_usecs(&myts);
tb->achieved_burns += GAME_RUN;
if (current_time > deadline) {
latency = current_time - deadline;
tb->missed_burns += latency;
} else
latency = 0;
if (latency > tb->max_latency)
tb->max_latency = latency;
tb->total_latency += latency;
tb->sum_latency_squared += latency * latency;
tb->nr_samples++;
if (!trywait_sem(s))
return;
}
}
void *burn_thread(void *t)
{
struct thread *th;
sem_t *s;
long i = (long)t;
th = &threadlist[i];
s = &th->sem.stopchild;
while (1) {
burn_loops(ud.loops_per_ms);
if (!trywait_sem(s)) {
post_sem(s);
break;
}
}
return NULL;
}
void emulate_burn(struct thread *th)
{
sem_t *s = &th->sem.stop;
unsigned long i;
long t;
pthread_t burnthreads[ud.cpu_load];
t = th->threadno;
for (i = 0 ; i < ud.cpu_load ; i++)
create_pthread(&burnthreads[i], NULL, burn_thread,
(void*)(long) t);
wait_sem(s);
post_sem(&th->sem.stopchild);
for (i = 0 ; i < ud.cpu_load ; i++)
join_pthread(burnthreads[i], NULL);
}
void emulate_write(struct thread *th)
{
sem_t *s = &th->sem.stop;
FILE *fp;
char *name = "interbench.write";
void *buf = NULL;
struct stat statbuf;
unsigned long mem;
if (!(fp = fopen(name, "w")))
terminal_error("fopen");
if (stat(name, &statbuf) == -1)
terminal_fileopen_error(fp, "stat");
if (statbuf.st_blksize < MIN_BLK_SIZE)
statbuf.st_blksize = MIN_BLK_SIZE;
mem = ud.ram / (statbuf.st_blksize / 1024);
if (!(buf = calloc(1, statbuf.st_blksize)))
terminal_fileopen_error(fp, "calloc");
if (fclose(fp) == -1)
terminal_error("fclose");
while (1) {
unsigned int i;
if (!(fp = fopen(name, "w")))
terminal_error("fopen");
if (stat(name, &statbuf) == -1)
terminal_fileopen_error(fp, "stat");
for (i = 0 ; i < mem; i++) {
if (fwrite(buf, statbuf.st_blksize, 1, fp) != 1)
terminal_fileopen_error(fp, "fwrite");
if (!trywait_sem(s))
goto out;
}
if (fclose(fp) == -1)
terminal_error("fclose");
}
out:
if (fclose(fp) == -1)
terminal_error("fclose");
if (remove(name) == -1)
terminal_error("remove");
sync_flush();
}
void emulate_read(struct thread *th)
{
sem_t *s = &th->sem.stop;
char *name = "interbench.read";
void *buf = NULL;
struct stat statbuf;
unsigned long bsize;
int tmp;
if ((tmp = open(name, O_RDONLY)) == -1)
terminal_error("open");
if (stat(name, &statbuf) == -1)
terminal_error("stat");
bsize = statbuf.st_blksize;
if (!(buf = malloc(bsize)))
terminal_error("malloc");
while (1) {
int rd;
while ((rd = Read(tmp , buf, bsize)) > 0);
if(!trywait_sem(s))
return;
if (lseek(tmp, (off_t)0, SEEK_SET) == -1)
terminal_error("lseek");
}
}
#define RINGTHREADS 4
struct thread ringthreads[RINGTHREADS];
void *ring_thread(void *t)
{
struct thread *th;
struct sems *s;
int i, post_to;
i = (long)t;
th = &ringthreads[i];
s = &th->sem;
post_to = i + 1;
if (post_to == RINGTHREADS)
post_to = 0;
if (i == 0)
post_sem(&s->ready);
while (1) {
wait_sem(&s->start);
post_sem(&ringthreads[post_to].sem.start);
if (!trywait_sem(&s->stop))
goto out;
}
out:
post_sem(&ringthreads[post_to].sem.start);
post_sem(&s->complete);
return NULL;
}
void emulate_ring(struct thread *th)
{
sem_t *s = &th->sem.stop;
int i;
for (i = 0 ; i < RINGTHREADS ; i++) {
init_all_sems(&ringthreads[i].sem);
create_pthread(&ringthreads[i].pthread, NULL,
ring_thread, (void*)(long) i);
}
wait_sem(&ringthreads[0].sem.ready);
post_sem(&ringthreads[0].sem.start);
wait_sem(s);
for (i = 0 ; i < RINGTHREADS ; i++)
post_sem(&ringthreads[i].sem.stop);
for (i = 0 ; i < RINGTHREADS ; i++) {
wait_sem(&ringthreads[i].sem.complete);
join_pthread(ringthreads[i].pthread, NULL);
}
}
void emulate_compile(struct thread *th)
{
sem_t *s = &th->sem.stop;
unsigned long i, threads[3];
bzero(threads, 3 * sizeof(threads[0]));
for (i = 0 ; i < THREADS ; i++) {
if (strcmp(threadlist[i].label, "Burn") == 0)
threads[0] = i;
if (strcmp(threadlist[i].label, "Write") == 0)
threads[1] = i;
if (strcmp(threadlist[i].label, "Read") == 0)
threads[2] = i;
}
for (i = 0 ; i < 3 ; i++) {
if (!threads[i]) {
fprintf(stderr, "Can't find all threads for compile load\n");
exit(1);
}
}
for (i = 0 ; i < 3 ; i++) {
initialise_thread(threads[i]);
start_thread(&threadlist[threads[i]]);
}
wait_sem(s);
for (i = 0 ; i < 3 ; i++)
stop_thread(&threadlist[threads[i]]);
}
int *grab_and_touch (char *block[], int i)
{
block[i] = (char *) malloc(MB);
if (!block[i])
return NULL;
return (memset(block[i], 1, MB));
}
void emulate_memload(struct thread *th)
{
sem_t *s = &th->sem.stop;
unsigned long touchable_mem, i;
char *mem_block[MAX_MEM_IN_MB];
void *success;
touchable_mem = compute_allocable_mem();
while (1) {
for (i = 0; i < touchable_mem; i++) {
success = grab_and_touch(mem_block, i);
if (!success) {
touchable_mem = i-1;
break;
}
}
if (!trywait_sem(s))
goto out_freemem;
for (i = 0; i < touchable_mem; i++) {
memcpy(mem_block[i], mem_block[(i + touchable_mem / 2) %
touchable_mem], MB);
if (!trywait_sem(s))
goto out_freemem;
}
for (i = 0; i < touchable_mem; i++) {
free(mem_block[i]);
}
if (!trywait_sem(s))
goto out;
}
out_freemem:
for (i = 0; i < touchable_mem; i++)
free(mem_block[i]);
out:
return;
}
struct thread hackthread;
void emulate_hackbench(struct thread *th)
{
sem_t *s = &th->sem.stop;
init_all_sems(&hackthread.sem);
create_pthread(&hackthread.pthread, NULL, hackbench_thread, (void *) 0);
wait_sem(s);
post_sem(&hackthread.sem.stop);
wait_sem(&hackthread.sem.complete);
join_pthread(hackthread.pthread, NULL);
}
#define CUSTOM_INTERVAL (ud.custom_interval)
#define CUSTOM_RUN (ud.custom_run)
void emulate_custom(struct thread *th)
{
unsigned long long deadline;
sem_t *s = &th->sem.stop;
struct timespec myts;
th->decasecond_deadlines = 1000000 / CUSTOM_INTERVAL * 10;
deadline = get_usecs(&myts);
while (1) {
deadline = periodic_schedule(th, CUSTOM_RUN, CUSTOM_INTERVAL,
deadline);
if (!trywait_sem(s))
return;
}
}
void *timekeeping_thread(void *t)
{
struct thread *th;
struct tk_thread *tk;
struct sems *s;
struct timespec myts;
long i = (long)t;
th = &threadlist[i];
tk = &th->tkthread;
s = &th->tkthread.sem;
if (th->dt != &th->benchmarks[NOT_BENCHING])
set_fifo(96);
tk->sleep_interval = tk->slept_interval = 0;
post_sem(&s->ready);
while (1) {
unsigned long start_time, now;
if (!trywait_sem(&s->stop))
goto out;
wait_sem(&s->start);
tk->slept_interval = 0;
start_time = get_usecs(&myts);
if (!trywait_sem(&s->stop))
goto out;
if (tk->sleep_interval) {
unsigned long diff = 0;
microsleep(tk->sleep_interval);
now = get_usecs(&myts);
if (now > start_time) {
diff = now - start_time;
if (diff > tk->sleep_interval)
tk->slept_interval = diff -
tk->sleep_interval;
}
}
tk->sleep_interval = 0;
post_sem(&s->complete);
}
out:
return NULL;
}
void *emulation_thread(void *t)
{
struct thread *th;
struct tk_thread *tk;
struct sems *s, *tks;
long i = (long)t;
th = &threadlist[i];
tk = &th->tkthread;
s = &th->sem;
tks = &tk->sem;
init_all_sems(tks);
create_pthread(&th->tk_pthread, NULL, timekeeping_thread,
(void*)(long) i);
wait_sem(&tks->ready);
post_sem(&s->ready);
wait_sem(&s->start);
th->name(th);
post_sem(&tks->stop);
post_sem(&tks->start);
join_pthread(th->tk_pthread, NULL);
post_sem(&s->complete);
return NULL;
}
void calibrate_loop(void)
{
unsigned long long start_time, loops_per_msec, run_time = 0;
unsigned long loops;
struct timespec myts;
loops_per_msec = 100000;
redo:
while (run_time > 1010000 || run_time < 990000) {
loops = loops_per_msec;
start_time = get_nsecs(&myts);
burn_loops(loops);
run_time = get_nsecs(&myts) - start_time;
loops_per_msec = (1000000 * loops_per_msec / run_time ? :
loops_per_msec);
}
sleep(1);
loops = loops_per_msec;
start_time = get_nsecs(&myts);
burn_loops(loops);
run_time = get_nsecs(&myts) - start_time;
if (run_time > 1050000 || run_time < 950000)
goto redo;
ud.loops_per_ms = loops_per_msec;
}
void log_output(const char *format, ...) __attribute__ ((format(printf, 1, 2)));
void log_output(const char *format, ...)
{
va_list ap;
va_start(ap, format);
if (vprintf(format, ap) == -1)
terminal_error("vprintf");
va_end(ap);
if (ud.log) {
va_start(ap, format);
if (vfprintf(ud.logfile, format, ap) == -1)
terminal_error("vpfrintf");
va_end(ap);
}
fflush(NULL);
}
void show_latencies(struct thread *th)
{
struct data_table *tbj;
struct tk_thread *tk;
double average_latency, deadlines_met, samples_met, sd, max_latency;
long double variance = 0;
tbj = th->dt;
tk = &th->tkthread;
if (tbj->nr_samples > 1) {
average_latency = tbj->total_latency / tbj->nr_samples;
variance = (tbj->sum_latency_squared - (average_latency *
average_latency) / tbj->nr_samples) / (tbj->nr_samples - 1);
sd = sqrt((double)variance);
} else {
average_latency = tbj->total_latency;
sd = 0.0;
}
if (tbj->achieved_burns > 0)
samples_met = (double)tbj->achieved_burns /
(double)(tbj->achieved_burns + tbj->missed_burns) * 100;
else
samples_met = 0.0;
max_latency = tbj->max_latency;
if (!ud.do_rt) {
average_latency /= 1000;
sd /= 1000;
max_latency /= 1000;
}
if (tbj->deadlines_met == 0)
deadlines_met = 0;
else
deadlines_met = (double)tbj->deadlines_met /
(double)(tbj->missed_deadlines + tbj->deadlines_met) * 100;
if (average_latency >= 100)
log_output("%7.0f +/- ", average_latency);
else
log_output("%7.3g +/- ", average_latency);
if (sd >= 100)
log_output("%-9.0f", sd);
else
log_output("%-9.3g", sd);
if (max_latency >= 100)
log_output("%7.0f\t", max_latency);
else
log_output("%7.3g\t", max_latency);
log_output("\t%4.3g", samples_met);
if (!th->nodeadlines)
log_output("\t%11.3g", deadlines_met);
log_output("\n");
sync_flush();
}
void create_read_file(void)
{
unsigned int i;
FILE *fp;
char *name = "interbench.read";
void *buf = NULL;
struct stat statbuf;
unsigned long mem, bsize;
int tmp;
if ((tmp = open(name, O_RDONLY)) == -1) {
if (errno != ENOENT)
terminal_error("open");
goto write;
}
if (stat(name, &statbuf) == -1)
terminal_error("stat");
if (statbuf.st_blksize < MIN_BLK_SIZE)
statbuf.st_blksize = MIN_BLK_SIZE;
bsize = statbuf.st_blksize;
if (statbuf.st_size / 1024 / bsize == ud.ram / bsize)
return;
if (remove(name) == -1)
terminal_error("remove");
write:
fprintf(stderr,"Creating file for read load...\n");
if (!(fp = fopen(name, "w")))
terminal_error("fopen");
if (stat(name, &statbuf) == -1)
terminal_fileopen_error(fp, "stat");
if (statbuf.st_blksize < MIN_BLK_SIZE)
statbuf.st_blksize = MIN_BLK_SIZE;
bsize = statbuf.st_blksize;
if (!(buf = calloc(1, bsize)))
terminal_fileopen_error(fp, "calloc");
mem = ud.ram / (bsize / 1024);
for (i = 0 ; i < mem; i++) {
if (fwrite(buf, bsize, 1, fp) != 1)
terminal_fileopen_error(fp, "fwrite");
}
if (fclose(fp) == -1)
terminal_error("fclose");
sync_flush();
}
void get_ram(void)
{
struct vmstats vms;
size_t vms_size = sizeof(vms);
if (sysctlbyname("vm.vmstats", &vms, &vms_size, NULL, 0))
terminal_error("sysctlbyname: vm.vmstats");
ud.ram = vms.v_page_count * vms.v_page_size;
ud.ram /= 1024;
ud.swap = ud.ram;
if( !ud.ram || !ud.swap ) {
unsigned long i;
fprintf(stderr, "\nCould not get memory or swap size. ");
fprintf(stderr, "Will not perform mem_load\n");
for (i = 0 ; i < THREADS ; i++) {
if (strcmp(threadlist[i].label, "Memload") == 0) {
threadlist[i].load = 0;
threadlist[i].rtload = 0;
}
}
}
}
void get_logfilename(void)
{
struct tm *mytm;
struct utsname buf;
time_t t;
int year, month, day, hours, minutes;
time(&t);
if (uname(&buf) == -1)
terminal_error("uname");
if (!(mytm = localtime(&t)))
terminal_error("localtime");
year = mytm->tm_year + 1900;
month = mytm->tm_mon + 1;
day = mytm->tm_mday;
hours = mytm->tm_hour;
minutes = mytm->tm_min;
strncpy(ud.unamer, buf.release, MAX_UNAME_LENGTH);
sprintf(ud.datestamp, "%2d%02d%02d%02d%02d",
year, month, day, hours, minutes);
snprintf(ud.logfilename, MAX_LOG_LENGTH, "%s.log", ud.unamer);
}
void start_thread(struct thread *th)
{
post_sem(&th->sem.start);
}
void stop_thread(struct thread *th)
{
post_sem(&th->sem.stop);
wait_sem(&th->sem.complete);
join_pthread(th->pthread, NULL);
}
void init_sem(sem_t *sem)
{
if (sem_init(sem, 0, 0))
terminal_error("sem_init");
}
void init_all_sems(struct sems *s)
{
init_sem(&s->ready);
init_sem(&s->start);
init_sem(&s->stop);
init_sem(&s->complete);
init_sem(&s->stopchild);
}
void initialise_thread(int i)
{
struct thread *th = &threadlist[i];
init_all_sems(&th->sem);
create_pthread(&th->pthread, NULL, emulation_thread, (void*)(long)i);
wait_sem(&th->sem.ready);
th->dt = &th->benchmarks[NOT_BENCHING];
initialise_thread_data(th->dt);
}
void wait_on(int pype)
{
int retval, buf = 0;
retval = Read(pype, &buf, sizeof(buf));
if (retval == 0) {
fprintf(stderr, "\nread returned 0\n");
exit (1);
}
}
void wakeup_with(int pype)
{
int retval, buf = 1;
retval = Write(pype, &buf, sizeof(buf));
if (retval == 0) {
fprintf(stderr, "\nwrite returned 0\n");
exit (1);
}
}
void run_loadchild(int j)
{
struct thread *thj;
thj = &threadlist[j];
set_nice(ud.load_nice);
initialise_thread(j);
wakeup_with(l2m[1]);
wait_on(m2l[0]);
start_thread(thj);
wakeup_with(l2m[1]);
wait_on(m2l[0]);
stop_thread(thj);
wakeup_with(l2m[1]);
exit (0);
}
void run_benchchild(int i, int j)
{
struct thread *thi;
thi = &threadlist[i];
set_nice(ud.bench_nice);
if (ud.do_rt)
set_mlock();
initialise_thread(i);
thi->dt = &thi->benchmarks[j];
initialise_thread_data(thi->dt);
if (ud.do_rt)
set_thread_fifo(thi->pthread, 95);
wakeup_with(b2m[1]);
wait_on(m2b[0]);
start_thread(thi);
wakeup_with(b2m[1]);
wait_on(m2b[0]);
stop_thread(thi);
if (ud.do_rt) {
set_thread_normal(thi->pthread);
set_munlock();
}
show_latencies(thi);
wakeup_with(b2m[1]);
exit(0);
}
void bench(int i, int j)
{
pid_t bench_pid, load_pid;
if ((load_pid = fork()) == -1)
terminal_error("fork");
if (!load_pid)
run_loadchild(j);
wait_on(l2m[0]);
if ((bench_pid = fork()) == -1)
terminal_error("fork");
if (!bench_pid)
run_benchchild(i, j);
wait_on(b2m[0]);
set_fifo(99);
set_mlock();
wakeup_with(m2l[1]);
wait_on(l2m[0]);
sleep(1);
wakeup_with(m2b[1]);
wait_on(b2m[0]);
microsleep(ud.duration * 1000000);
wakeup_with(m2b[1]);
wakeup_with(m2l[1]);
set_normal();
set_munlock();
wait_on(l2m[0]);
wait_on(b2m[0]);
}
void init_pipe(int *pype)
{
if (pipe(pype) == -1)
terminal_error("pipe");
}
void init_pipes(void)
{
init_pipe(m2l);
init_pipe(l2m);
init_pipe(m2b);
init_pipe(b2m);
}
void usage(void)
{
fprintf(stderr, "interbench v " INTERBENCH_VERSION " by Con Kolivas\n");
fprintf(stderr, "interbench [-l <int>] [-L <int>] [-t <int] [-B <int>] [-N <int>]\n");
fprintf(stderr, "\t[-b] [-c] [-r] [-C <int> -I <int>] [-m <comment>]\n");
fprintf(stderr, "\t[-w <load type>] [-x <load type>] [-W <bench>] [-X <bench>]\n");
fprintf(stderr, "\t[-h]\n\n");
fprintf(stderr, " -l\tUse <int> loops per sec (default: use saved benchmark)\n");
fprintf(stderr, " -L\tUse cpu load of <int> with burn load (default: 4)\n");
fprintf(stderr, " -t\tSeconds to run each benchmark (default: 30)\n");
fprintf(stderr, " -B\tNice the benchmarked thread to <int> (default: 0)\n");
fprintf(stderr, " -N\tNice the load thread to <int> (default: 0)\n");
fprintf(stderr, " -b\tBenchmark loops_per_ms even if it is already known\n");
fprintf(stderr, " -c\tOutput to console only (default: use console and logfile)\n");
fprintf(stderr, " -r\tPerform real time scheduling benchmarks (default: non-rt)\n");
fprintf(stderr, " -C\tUse <int> percentage cpu as a custom load (default: no custom load)\n");
fprintf(stderr, " -I\tUse <int> microsecond intervals for custom load (needs -C as well)\n");
fprintf(stderr, " -m\tAdd <comment> to the log file as a separate line\n");
fprintf(stderr, " -w\tAdd <load type> to the list of loads to be tested against\n");
fprintf(stderr, " -x\tExclude <load type> from the list of loads to be tested against\n");
fprintf(stderr, " -W\tAdd <bench> to the list of benchmarks to be tested\n");
fprintf(stderr, " -X\tExclude <bench> from the list of benchmarks to be tested\n");
fprintf(stderr, " -h\tShow this help\n");
fprintf(stderr, "\nIf run without parameters interbench will run a standard benchmark\n\n");
}
#ifdef DEBUG
void deadchild(int crap)
{
pid_t retval;
int status;
crap = 0;
if ((retval = waitpid(-1, &status, WNOHANG)) == -1) {
if (errno == ECHILD)
return;
terminal_error("waitpid");
}
if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
return;
fprintf(stderr, "\nChild terminated abnormally ");
if (WIFSIGNALED(status))
fprintf(stderr, "with signal %d", WTERMSIG(status));
fprintf(stderr, "\n");
exit (1);
}
#endif
int load_index(const char* loadname)
{
int i;
for (i = 0 ; i < THREADS ; i++)
if (strcasecmp(loadname, threadlist[i].label) == 0)
return i;
return -1;
}
inline int bit_is_on(const unsigned int mask, int index)
{
return (mask & (1 << index)) != 0;
}
inline void set_bit_on(unsigned int *mask, int index)
{
*mask |= (1 << index);
}
int main(int argc, char **argv)
{
unsigned long custom_cpu = 0;
int q, i, j, affinity, benchmark = 0;
unsigned int selected_loads = 0;
unsigned int excluded_loads = 0;
unsigned int selected_benches = 0;
unsigned int excluded_benches = 0;
FILE *fp;
char *fname = "interbench.loops_per_ms";
char *comment = NULL;
#ifdef DEBUG
feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW);
if (signal(SIGCHLD, deadchild) == SIG_ERR)
terminal_error("signal");
#endif
while ((q = getopt(argc, argv, "hl:L:B:N:ut:bcnrC:I:m:w:x:W:X:")) != -1) {
switch (q) {
case 'h':
usage();
return (0);
case 'l':
ud.loops_per_ms = atoi(optarg);
break;
case 't':
ud.duration = atoi(optarg);
break;
case 'L':
ud.cpu_load = atoi(optarg);
break;
case 'B':
ud.bench_nice = atoi(optarg);
break;
case 'N':
ud.load_nice = atoi(optarg);
break;
case 'u':
affinity = 1;
break;
case 'b':
benchmark = 1;
break;
case 'c':
ud.log = 0;
break;
case 'r':
ud.do_rt = 1;
break;
case 'C':
custom_cpu = (unsigned long)atol(optarg);
break;
case 'I':
ud.custom_interval = atol(optarg);
break;
case 'm':
comment = optarg;
break;
case 'w':
i = load_index(optarg);
if (i == -1) {
fprintf(stderr, "Unknown load \"%s\"\n", optarg);
return (-2);
}
set_bit_on(&selected_loads, i);
break;
case 'x':
i = load_index(optarg);
if (i == -1) {
fprintf(stderr, "Unknown load \"%s\"\n", optarg);
return (-2);
}
set_bit_on(&excluded_loads, i);
break;
case 'W':
i = load_index(optarg);
if (i == -1) {
fprintf(stderr, "Unknown bench \"%s\"\n", optarg);
return (-2);
}
set_bit_on(&selected_benches, i);
break;
case 'X':
i = load_index(optarg);
if (i == -1) {
fprintf(stderr, "Unknown bench \"%s\"\n", optarg);
return (-2);
}
set_bit_on(&excluded_benches, i);
break;
default:
usage();
return (1);
}
}
argc -= optind;
argv += optind;
if (selected_loads == 0)
selected_loads = (unsigned int)-1;
selected_loads &= ~excluded_loads;
if (selected_benches == 0)
selected_benches = (unsigned int)-1;
selected_benches &= ~excluded_benches;
if (!test_fifo()) {
fprintf(stderr, "Unable to get SCHED_FIFO (real time scheduling).\n");
fprintf(stderr, "You either need to run this as root user or have support for real time RLIMITS.\n");
if (ud.do_rt) {
fprintf(stderr, "Real time tests were requested, aborting.\n");
exit (1);
}
fprintf(stderr, "Results will be unreliable.\n");
}
if (!ud.cpu_load) {
fprintf(stderr, "Invalid cpu load\n");
exit (1);
}
if ((custom_cpu && !ud.custom_interval) ||
(ud.custom_interval && !custom_cpu) ||
custom_cpu > 100) {
fprintf(stderr, "Invalid custom values, aborting.\n");
exit (1);
}
if (custom_cpu && ud.custom_interval) {
ud.custom_run = ud.custom_interval * custom_cpu / 100;
threadlist[CUSTOM].bench = 1;
threadlist[CUSTOM].load = 1;
threadlist[CUSTOM].rtbench = 1;
threadlist[CUSTOM].rtload = 1;
}
#if 0
if (affinity) {
#ifdef CPU_SET
cpu_set_t cpumask;
CPU_ZERO(&cpumask);
CPU_SET(0, &cpumask);
#else
unsigned long cpumask = 1;
#endif
if (sched_setaffinity(0, sizeof(cpumask), &cpumask) == -1) {
if (errno != EPERM)
terminal_error("sched_setaffinity");
fprintf(stderr, "could not set cpu affinity\n");
}
}
#endif
if (ud.duration % 10)
ud.duration += 10 - ud.duration % 10;
if (benchmark)
ud.loops_per_ms = 0;
if (!ud.loops_per_ms) {
if (benchmark)
goto bench;
if ((fp = fopen(fname, "r"))) {
fscanf(fp, "%lu", &ud.loops_per_ms);
if (fclose(fp) == -1)
terminal_error("fclose");
if (ud.loops_per_ms) {
fprintf(stderr,
"%lu loops_per_ms read from file interbench.loops_per_ms\n",
ud.loops_per_ms);
goto loops_known;
}
} else
if (errno != ENOENT)
terminal_error("fopen");
bench:
fprintf(stderr, "loops_per_ms unknown; benchmarking...\n");
set_fifo(99);
calibrate_loop();
set_normal();
} else
fprintf(stderr, "loops_per_ms specified from command line\n");
if (!(fp = fopen(fname, "w"))) {
if (errno != EACCES)
terminal_error("fopen");
fprintf(stderr, "Unable to write to file interbench.loops_per_ms\n");
goto loops_known;
}
fprintf(fp, "%lu", ud.loops_per_ms);
fprintf(stderr, "%lu loops_per_ms saved to file interbench.loops_per_ms\n",
ud.loops_per_ms);
if (fclose(fp) == -1)
terminal_error("fclose");
loops_known:
get_ram();
get_logfilename();
create_read_file();
init_pipes();
if (ud.log && !(ud.logfile = fopen(ud.logfilename, "a"))) {
if (errno != EACCES)
terminal_error("fopen");
fprintf(stderr, "Unable to write to logfile\n");
ud.log = 0;
}
log_output("\n");
log_output("Using %lu loops per ms, running every load for %d seconds\n",
ud.loops_per_ms, ud.duration);
log_output("Benchmarking kernel %s at datestamp %s\n",
ud.unamer, ud.datestamp);
if (comment)
log_output("Comment: %s\n", comment);
log_output("\n");
for (i = 0 ; i < THREADS ; i++)
threadlist[i].threadno = i;
for (i = 0 ; i < THREADS ; i++) {
struct thread *thi = &threadlist[i];
int *benchme;
if (ud.do_rt)
benchme = &threadlist[i].rtbench;
else
benchme = &threadlist[i].bench;
if (!*benchme || !bit_is_on(selected_benches, i))
continue;
log_output("--- Benchmarking simulated cpu of %s ", threadlist[i].label);
if (ud.do_rt)
log_output("real time ");
else if (ud.bench_nice)
log_output("nice %d ", ud.bench_nice);
log_output("in the presence of simulated ");
if (ud.load_nice)
log_output("nice %d ", ud.load_nice);
log_output("---\n");
log_output("Load");
if (ud.do_rt)
log_output("\tLatency +/- SD (us)");
else
log_output("\tLatency +/- SD (ms)");
log_output(" Max Latency ");
log_output(" %% Desired CPU");
if (!thi->nodeadlines)
log_output(" %% Deadlines Met");
log_output("\n");
for (j = 0 ; j < THREADS ; j++) {
struct thread *thj = &threadlist[j];
if (j == i || !bit_is_on(selected_loads, j) ||
(!threadlist[j].load && !ud.do_rt) ||
(!threadlist[j].rtload && ud.do_rt))
continue;
log_output("%s\t", thj->label);
sync_flush();
bench(i, j);
}
log_output("\n");
}
log_output("\n");
if (ud.log)
fclose(ud.logfile);
return 0;
}