Viewing: iabf.c
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include "callvpe.h"
enum {
NSEC_PER_SEC = 1000000000L,
};
static int iabf_debug = false;
static const char iabf_delim[] = "---";
static __thread pid_t iabf_tid;
#define DEBUG(fmt, args...) \
do { \
if (iabf_debug) \
fprintf(stderr, "DEBUG [%d] %s:%d: "fmt, iabf_tid, __func__, __LINE__, ##args); \
} while (0)
#define DEBUG_B(x) DEBUG("%s = %s\n", #x, (x) ? "true" : "false")
#define DEBUG_D(x) DEBUG("%s = %"PRIdMAX"\n", #x, (intmax_t)(x))
#define DEBUG_P(x) DEBUG("%s = %p\n", #x, (x))
#define DEBUG_S(x) DEBUG("%s = '%s'\n", #x, (x))
#define DEBUG_U(x) DEBUG("%s = %"PRIuMAX"\n", #x, (uintmax_t)(x))
#define DEBUG_X(x) DEBUG("%s = %"PRIxMAX"\n", #x, (uintmax_t)(x))
#define ERROR(fmt, args...) \
fprintf(stderr, "%s: "fmt, program_invocation_short_name, ##args)
#define FATAL(fmt, args...) \
do { \
ERROR("fatal: "fmt, ##args); \
exit(EXIT_FAILURE); \
} while (0)
#define xstrerror(e) strerror(abs(e))
static struct timespec
timespec_sub(struct timespec a, struct timespec b)
{
struct timespec r = {
.tv_sec = a.tv_sec - b.tv_sec,
.tv_nsec = a.tv_nsec - b.tv_nsec,
};
while (r.tv_nsec >= NSEC_PER_SEC) {
r.tv_sec++;
r.tv_nsec -= NSEC_PER_SEC;
}
while (r.tv_nsec < 0) {
r.tv_sec--;
r.tv_nsec += NSEC_PER_SEC;
}
return r;
}
static struct timespec
timespec_from_ns(long ns)
{
return (struct timespec) {
.tv_sec = ns / NSEC_PER_SEC,
.tv_nsec = ns % NSEC_PER_SEC,
};
}
static long timespec_to_ns(struct timespec tv)
{
return tv.tv_sec * NSEC_PER_SEC + tv.tv_nsec;
}
struct iabf_control {
char **ic_init;
char **ic_fini;
cpu_set_t *ic_affinity;
long ic_delay_begin_ns;
long ic_delay_end_ns;
long ic_delay_step_ns;
long ic_step_count;
long ic_autotune_count;
pthread_barrier_t ic_barrier[2];
int ic_should_stop;
};
struct iabf_task {
struct iabf_control *it_control;
const char *it_name;
pthread_t it_thread;
struct timespec it_delay;
struct timespec it_elapsed;
char **it_argv;
};
static long iabf_getenvl(const char *name, long def)
{
const char *s = getenv(name);
return s != NULL ? atol(s) : def;
}
static void iabf_barrier_wait(struct iabf_control *ic, int which)
{
int rc;
static_assert(PTHREAD_BARRIER_SERIAL_THREAD == -1, "PTHREAD_BARRIER_SERIAL_THREAD must be -1");
rc = pthread_barrier_wait(&ic->ic_barrier[which]);
if (rc > 0)
FATAL("cannot wait on barrier: %s\n", xstrerror(rc));
}
static void *iabf_task_thread(void *data)
{
struct iabf_task *it = data;
struct iabf_control *ic = it->it_control;
int rc;
iabf_tid = syscall(SYS_gettid);
static_assert(PTHREAD_BARRIER_SERIAL_THREAD == -1, "PTHREAD_BARRIER_SERIAL_THREAD must be -1");
while (1) {
struct timespec ts[2];
pid_t pid, pid2;
int status;
iabf_barrier_wait(ic, 0);
DEBUG_D(ic->ic_should_stop);
if (ic->ic_should_stop)
break;
rc = clock_nanosleep(CLOCK_MONOTONIC, 0, &it->it_delay, NULL);
if (rc != 0)
FATAL("%s: cannot sleep: %s\n", it->it_name, xstrerror(rc));
rc = clock_gettime(CLOCK_MONOTONIC, &ts[0]);
if (rc != 0)
FATAL("%s: cannot get time: %s\n", it->it_name, xstrerror(errno));
pid = fork();
if (pid < 0)
FATAL("%s: cannot fork: %s\n", it->it_name, strerror(errno));
if (pid == 0) {
execvpe(it->it_argv[0], it->it_argv, environ);
_exit(127);
}
pid2 = waitpid(pid, &status, 0);
if (pid2 < 0)
FATAL("%s: cannot wait for pid %d: %s\n", it->it_name, (int)pid, strerror(errno));
rc = clock_gettime(CLOCK_MONOTONIC, &ts[1]);
if (rc != 0)
FATAL("%s: cannot get time: %s\n", it->it_name, xstrerror(errno));
it->it_elapsed = timespec_sub(ts[1], ts[0]);
assert(pid == pid2);
DEBUG("%s: cmd = '%s', pid = %d, status = %d, elapsed_ns = %ld\n",
it->it_name, it->it_argv[0], pid, status, timespec_to_ns(it->it_elapsed));
if (WIFEXITED(status) && WEXITSTATUS(status) == 127)
FATAL("%s: command '%s' (pid %d) exited with status 127\n", it->it_name, it->it_argv[0], pid);
iabf_barrier_wait(ic, 1);
}
return NULL;
}
/* Run I, A+B, F once. Task threads must already be started.
*
* If delay_ns < 0 then
* delay exec of A by labs(delay_ns) nsec
* else
* delay exec of B by labs(delay_ns) nsec.
*/
static int iabf_step(struct iabf_control *ic,
struct iabf_task it[2],
long delay_ns)
{
int rc;
if (ic->ic_init != NULL && ic->ic_init[0] != NULL) {
rc = callvpe(ic->ic_init[0], ic->ic_init, environ);
DEBUG_D(rc); /* waitpid status */
if (rc != 0)
FATAL("initializer '%s' terminated with status %d\n", ic->ic_init[0], rc);
}
DEBUG_D(delay_ns);
if (delay_ns < 0) {
it[0].it_delay = timespec_from_ns(labs(delay_ns));
it[1].it_delay = timespec_from_ns(0);
} else {
it[0].it_delay = timespec_from_ns(0);
it[1].it_delay = timespec_from_ns(labs(delay_ns));
}
iabf_barrier_wait(ic, 0);
/* A+B run here. */
iabf_barrier_wait(ic, 1);
if (ic->ic_fini != NULL && ic->ic_fini[0] != NULL) {
rc = callvpe(ic->ic_fini[0], ic->ic_fini, environ);
DEBUG_D(rc); /* waitpid status */
if (rc != 0)
FATAL("finalizer '%s' terminated with status %d\n", ic->ic_fini[0], rc);
}
return 0;
}
/* Run (I, A+B, F) step $IABF_AUTOTUNE_COUNT times. Task threads must
* already be started. Get the average elapsed times for A and B. We
* want to choose delay_begin and delay_end to try to arrange all
* possible overlaps given the expected elapsed times of A and B.
*
* AAAAAAAAAA delay(A) is approx elapsed(B)
* BBBBB delay(B) == 0
*
* AAAAAAAAAA delay(A) == 0
* BBBBB delay(B) == 0
*
* AAAAAAAAAA delay(A) == 0
* BBBBB delay(B) is approx elapsed(A)
*
* Note that to delay task A we use a negative delay_ns.
*/
static int iabf_autotune(struct iabf_control *ic,
struct iabf_task it[2])
{
long elapsed_ns[2] = { 0, 0 };
long i, j;
DEBUG("begin autotune\n");
assert(ic->ic_autotune_count >= 0);
if (ic->ic_autotune_count == 0)
return 0;
for (i = 0; i < ic->ic_autotune_count; i++) {
iabf_step(ic, it, 0);
for (j = 0; j < 2; j++)
elapsed_ns[j] += timespec_to_ns(it[j].it_elapsed);
}
elapsed_ns[0] /= ic->ic_autotune_count;
elapsed_ns[1] /= ic->ic_autotune_count;
DEBUG_D(elapsed_ns[0]);
DEBUG_D(elapsed_ns[1]);
assert(0 <= elapsed_ns[0]);
assert(0 <= elapsed_ns[1]);
/* TODO Apply a multiplier to endpoints. */
if (ic->ic_delay_begin_ns == LONG_MIN)
ic->ic_delay_begin_ns = -elapsed_ns[1];
if (ic->ic_delay_end_ns == LONG_MAX)
ic->ic_delay_end_ns = +elapsed_ns[0];
assert(ic->ic_delay_begin_ns <= ic->ic_delay_end_ns);
assert(0 <= ic->ic_step_count);
if (ic->ic_step_count != 0)
ic->ic_delay_step_ns = (ic->ic_delay_end_ns - ic->ic_delay_begin_ns) / ic->ic_step_count;
if (ic->ic_delay_step_ns == 0)
ic->ic_delay_step_ns = 1; /* Or just leave it 0? */
DEBUG("end autotune\n");
return 0;
}
/* Start A and B threads, autotune delay parameters if needed, run
* iabf_step() however many times. */
static int iabf(struct iabf_control *ic, char **a, char **b)
{
struct iabf_task it[2] = {
[0] = {
.it_control = ic,
.it_name = "A",
.it_argv = a,
},
[1] = {
.it_control = ic,
.it_name = "B",
.it_argv = b,
},
};
pthread_attr_t attr_, *attr = NULL;
long i;
int rc;
rc = pthread_attr_init(&attr_);
if (rc != 0)
FATAL("cannot initialize thread attributes: %s\n", xstrerror(rc));
attr = &attr_;
for (i = 0; i < 2; i++) {
rc = pthread_barrier_init(&ic->ic_barrier[i], NULL, 3);
if (rc != 0)
FATAL("cannot initialize barrier: %s\n", xstrerror(rc));
}
if (ic->ic_affinity != NULL) {
rc = pthread_setaffinity_np(pthread_self(), sizeof(ic->ic_affinity[2]), &ic->ic_affinity[2]);
if (rc != 0)
FATAL("cannot set CPU affinity : %s\n", xstrerror(rc));
}
for (i = 0; i < 2; i++) {
if (ic->ic_affinity != NULL) {
rc = pthread_attr_setaffinity_np(attr, sizeof(ic->ic_affinity[i]), &ic->ic_affinity[i]);
if (rc != 0)
FATAL("cannot set thread attr CPU affinity : %s\n", xstrerror(rc));
}
rc = pthread_create(&it[i].it_thread,
attr,
iabf_task_thread,
&it[i]);
if (rc != 0)
FATAL("cannot create thread: %s\n", xstrerror(rc));
}
if (ic->ic_delay_begin_ns == LONG_MIN ||
ic->ic_delay_end_ns == LONG_MAX ||
ic->ic_delay_step_ns == 0)
iabf_autotune(ic, it);
DEBUG_D(ic->ic_delay_begin_ns);
DEBUG_D(ic->ic_delay_end_ns);
DEBUG_D(ic->ic_delay_step_ns);
long delay_ns;
for (delay_ns = ic->ic_delay_begin_ns;
delay_ns < ic->ic_delay_end_ns;
delay_ns += ic->ic_delay_step_ns)
iabf_step(ic, it, delay_ns);
ic->ic_should_stop = 1;
DEBUG_D(ic->ic_should_stop);
iabf_barrier_wait(ic, 0);
for (i = 0; i < 2; i++) {
rc = pthread_join(it[i].it_thread, NULL);
if (rc != 0)
FATAL("cannot join thread %s: %s\n", it[i].it_name, xstrerror(rc));
}
for (i = 0; i < 2; i++) {
rc = pthread_barrier_destroy(&ic->ic_barrier[i]);
if (rc != 0)
FATAL("cannot destroy barrier: %s\n", xstrerror(rc));
}
if (attr != NULL)
pthread_attr_destroy(attr);
return 0;
}
/* strsep() for argvs */
static char **arg_sep(char ***pargs, const char *delim)
{
char **begin, **end;
begin = *pargs;
if (begin == NULL)
return NULL;
/* Find the end of the token. */
/* end = begin + strcspn (begin, delim); */
for (end = begin; *end != NULL && strcmp(*end, delim) != 0; end++)
;
if (*end != NULL) {
/* Terminate the token and set *STRINGP past NUL character. */
*end++ = NULL;
*pargs = end;
} else {
/* No more delimiters; this is the last token. */
*pargs = NULL;
}
return begin;
}
static cpu_set_t *iabf_affinity(const char *str)
{
cpu_set_t *cpu_sets = NULL;
char *str1 = NULL;
char *p;
char *q;
char *r;
int i;
if (str == NULL)
return NULL;
cpu_sets = calloc(3, sizeof(cpu_sets[0]));
p = str1 = strdup(str);
for (i = 0; i < 3; i++) {
CPU_ZERO(&cpu_sets[i]);
q = strsep(&p, " ");
if (q == NULL)
FATAL("invalid affinity '%s'\n", str);
while ((r = strsep(&q, ",")) != NULL)
CPU_SET(atoi(r), &cpu_sets[i]);
}
if (p != NULL)
FATAL("invalid affinity '%s'\n", str);
free(str1);
return cpu_sets;
}
int main(int argc, char **argv)
{
struct iabf_control ic = {
.ic_should_stop = 0,
};
char **args = argv + 1;
char **a;
char **b;
iabf_tid = syscall(SYS_gettid);
iabf_debug = atoi(getenv("IABF_DEBUG") ?: "0");
ic.ic_init = arg_sep(&args, iabf_delim);
a = arg_sep(&args, iabf_delim);
b = arg_sep(&args, iabf_delim);
ic.ic_fini = arg_sep(&args, iabf_delim);
if (ic.ic_init == NULL ||
a == NULL ||
b == NULL ||
ic.ic_fini == NULL)
FATAL("missing '%s' in argv\n", iabf_delim);
int i;
for (i = 0; ic.ic_init[i] != NULL; i++)
DEBUG_S(ic.ic_init[i]);
for (i = 0; a[i] != NULL; i++)
DEBUG_S(a[i]);
for (i = 0; b[i] != NULL; i++)
DEBUG_S(b[i]);
for (i = 0; ic.ic_fini[i] != NULL; i++)
DEBUG_S(ic.ic_fini[i]);
ic.ic_affinity = iabf_affinity(getenv("IABF_AFFINITY"));
ic.ic_delay_begin_ns = iabf_getenvl("IABF_DELAY_BEGIN_NS", LONG_MIN);
ic.ic_delay_end_ns = iabf_getenvl("IABF_DELAY_END_NS", LONG_MAX);
ic.ic_delay_step_ns = iabf_getenvl("IABF_DELAY_STEP_NS", 0);
ic.ic_step_count = iabf_getenvl("IABF_STEP_COUNT", 0);
ic.ic_autotune_count = iabf_getenvl("IABF_AUTOTUNE_COUNT", 16);
DEBUG_D(ic.ic_delay_begin_ns);
DEBUG_D(ic.ic_delay_end_ns);
DEBUG_D(ic.ic_delay_step_ns);
DEBUG_D(ic.ic_step_count);
DEBUG_D(ic.ic_autotune_count);
assert(ic.ic_delay_begin_ns <= ic.ic_delay_end_ns);
assert(0 <= ic.ic_delay_step_ns);
assert(0 <= ic.ic_step_count);
iabf(&ic, a, b);
return 0;
}