From f5db3a98148d887340937eedda6818dd4db504d6 Mon Sep 17 00:00:00 2001 From: Rodrigo Arias Date: Wed, 25 Jan 2023 12:01:01 +0100 Subject: [PATCH] Add cpu module and begin ovni model --- src/common.c | 59 ++ src/{include => }/common.h | 10 +- src/emu/CMakeLists.txt | 10 + src/emu/chan.c | 17 +- src/emu/chan.h | 2 +- src/emu/cpu.c | 105 +++ src/emu/cpu.h | 53 ++ src/emu/emu.c | 52 +- src/emu/emu.h | 37 +- src/emu/emu_ev.c | 31 + src/emu/emu_ev.h | 29 + src/emu/emu_model.c | 27 + src/emu/emu_model.h | 29 + src/emu/emu_player.c | 80 ++- src/emu/emu_player.h | 6 +- src/emu/emu_system.c | 66 +- src/emu/emu_system.h | 42 +- src/emu/emu_system_thread.c | 6 + src/emu/loom.c | 39 + src/emu/loom.h | 46 ++ src/emu/nanos6/nanos6_model.h | 69 ++ src/emu/old_emu.c | 1250 +++++++++++++++++++++++++++++++++ src/emu/old_emu.h | 586 ++++++++++++++++ src/emu/old_trace.c | 686 ++++++++++++++++++ src/emu/old_trace.h | 20 + src/emu/ovni/CMakeLists.txt | 15 + src/emu/ovni/connect.c | 95 +++ src/emu/ovni/create.c | 110 +++ src/emu/ovni/event.c | 516 ++++++++++++++ src/emu/ovni/ovni_model.h | 59 ++ src/emu/ovni/probe.c | 14 + src/emu/ovni/trace.c | 672 ++++++++++++++++++ src/emu/ovni/trace.h | 16 + src/emu/proc.c | 112 +++ src/emu/proc.h | 47 ++ src/emu/pvtrace.c | 0 src/emu/pvtrace.h | 30 + src/emu/thread.c | 120 ++++ src/emu/thread.h | 77 ++ 39 files changed, 5075 insertions(+), 165 deletions(-) create mode 100644 src/common.c rename src/{include => }/common.h (73%) create mode 100644 src/emu/cpu.c create mode 100644 src/emu/cpu.h create mode 100644 src/emu/emu_ev.c create mode 100644 src/emu/emu_ev.h create mode 100644 src/emu/emu_model.c create mode 100644 src/emu/emu_model.h create mode 100644 src/emu/emu_system_thread.c create mode 100644 src/emu/loom.c create mode 100644 src/emu/loom.h create mode 100644 src/emu/nanos6/nanos6_model.h create mode 100644 src/emu/old_emu.c create mode 100644 src/emu/old_emu.h create mode 100644 src/emu/old_trace.c create mode 100644 src/emu/old_trace.h create mode 100644 src/emu/ovni/CMakeLists.txt create mode 100644 src/emu/ovni/connect.c create mode 100644 src/emu/ovni/create.c create mode 100644 src/emu/ovni/event.c create mode 100644 src/emu/ovni/ovni_model.h create mode 100644 src/emu/ovni/probe.c create mode 100644 src/emu/ovni/trace.c create mode 100644 src/emu/ovni/trace.h create mode 100644 src/emu/proc.c create mode 100644 src/emu/proc.h create mode 100644 src/emu/pvtrace.c create mode 100644 src/emu/pvtrace.h create mode 100644 src/emu/thread.c create mode 100644 src/emu/thread.h diff --git a/src/common.c b/src/common.c new file mode 100644 index 0000000..d4b67c0 --- /dev/null +++ b/src/common.c @@ -0,0 +1,59 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: MIT */ + +#include "common.h" + +#include +#include +#include +#include + +char *progname = NULL; + +void +progname_set(char *name) +{ + progname = name; +} + +static void +vaerr(const char *func, const char *errstr, va_list ap) +{ + if (progname != NULL) + fprintf(stderr, "%s: ", progname); + + if (func != NULL) + fprintf(stderr, "%s: ", func); + + vfprintf(stderr, errstr, ap); + + int len = strlen(errstr); + + if (len > 0) { + char last = errstr[len - 1]; + if (last == ':') + fprintf(stderr, " %s\n", strerror(errno)); + else if (last != '\n') + fprintf(stderr, "\n"); + } +} + +void +verr(const char *func, const char *errstr, ...) +{ + va_list ap; + va_start(ap, errstr); + vaerr(func, errstr, ap); + va_end(ap); +} + + +void +vdie(const char *func, const char *errstr, ...) +{ + va_list ap; + va_start(ap, errstr); + vaerr(func, errstr, ap); + va_end(ap); + abort(); +} diff --git a/src/include/common.h b/src/common.h similarity index 73% rename from src/include/common.h rename to src/common.h index 068635e..7206556 100644 --- a/src/include/common.h +++ b/src/common.h @@ -9,6 +9,10 @@ /* Debug macros */ +void progname_set(char *name); +void verr(const char *func, const char *errstr, ...); +void vdie(const char *func, const char *errstr, ...); + /* clang-format off */ #ifdef ENABLE_DEBUG @@ -17,8 +21,8 @@ # define dbg(...) #endif -#define err(...) fprintf(stderr, __VA_ARGS__); -#define die(...) do { err("fatal: " __VA_ARGS__); abort(); } while (0) +#define err(...) verr(__func__, __VA_ARGS__); +#define die(...) vdie(__func__, __VA_ARGS__); #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) @@ -33,4 +37,6 @@ /* clang-format on */ + + #endif /* COMMON_H */ diff --git a/src/emu/CMakeLists.txt b/src/emu/CMakeLists.txt index c527113..2dd432f 100644 --- a/src/emu/CMakeLists.txt +++ b/src/emu/CMakeLists.txt @@ -12,17 +12,27 @@ include_directories( #target_link_libraries(trace parson ovni-static) add_library(emu STATIC + ../common.c + cpu.c + thread.c emu.c emu_system.c + emu_system_thread.c emu_args.c emu_stream.c emu_trace.c emu_player.c + emu_model.c + emu_ev.c chan.c bay.c mux.c prv.c clkoff.c + ovni/probe.c + ovni/create.c + ovni/connect.c + ovni/event.c ) add_subdirectory(ovni) diff --git a/src/emu/chan.c b/src/emu/chan.c index 95998cc..1cca152 100644 --- a/src/emu/chan.c +++ b/src/emu/chan.c @@ -6,14 +6,19 @@ #include void -chan_init(struct chan *chan, enum chan_type type, const char *name) +chan_init(struct chan *chan, enum chan_type type, const char *fmt, ...) { - int len = strlen(name); - if (len >= MAX_CHAN_NAME) - die("chan_init: name '%s' too long\n", name); + memset(chan, 0, sizeof(struct chan)); + + va_list ap; + va_start(ap, fmt); + + int n = ARRAYLEN(chan->name); + int ret = vsnprintf(chan->name, n, fmt, ap); + if (ret >= n) + die("channel name too long\n"); + va_end(ap); - memset(chan, 0, sizeof(*chan)); - memcpy(chan->name, name, len + 1); chan->type = type; } diff --git a/src/emu/chan.h b/src/emu/chan.h index 02705c9..59e1a1a 100644 --- a/src/emu/chan.h +++ b/src/emu/chan.h @@ -48,7 +48,7 @@ struct chan { void *dirty_arg; }; - void chan_init(struct chan *chan, enum chan_type type, const char *name); + void chan_init(struct chan *chan, enum chan_type type, const char *fmt, ...); USE_RET int chan_set(struct chan *chan, struct value value); USE_RET int chan_push(struct chan *chan, struct value value); USE_RET int chan_pop(struct chan *chan, struct value expected); diff --git a/src/emu/cpu.c b/src/emu/cpu.c new file mode 100644 index 0000000..1f22327 --- /dev/null +++ b/src/emu/cpu.c @@ -0,0 +1,105 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "cpu.h" + +#include "chan.h" +#include "value.h" +#include "utlist.h" + +void +cpu_init(struct cpu *cpu, int i, int phyid, int is_virtual) +{ + memset(cpu, 0, sizeof(struct cpu)); + + cpu->i = i; + cpu->phyid = phyid; + cpu->is_virtual = is_virtual; +} + +void +cpu_set_gindex(struct cpu *cpu, int64_t gindex) +{ + cpu->gindex = gindex; +} + +void +cpu_set_name(struct cpu *cpu, int64_t loom) +{ + int n; + if (cpu->is_virtual) + n = snprintf(cpu->name, PATH_MAX, "vCPU %ld.*", loom); + else + n = snprintf(cpu->name, PATH_MAX, "CPU %ld.%d", loom, cpu->i); + + /* Unlikely */ + if (n >= PATH_MAX) + die("cpu_set_name: cpu name too long\n"); +} + +static struct thread * +find_thread(struct cpu *cpu, struct thread *thread) +{ + struct thread *p = NULL; + + DL_FOREACH2(cpu->thread, p, cpu_next) + { + if (p == thread) + return p; + } + + return NULL; +} + +static int +update_cpu(struct cpu *cpu) +{ + struct thread *th = NULL; + struct thread *th_running = NULL; + struct thread *th_active = NULL; + int active = 0, running = 0; + + DL_FOREACH2(cpu->thread, th, cpu_next) + { + if (th->state == TH_ST_RUNNING) { + th_running = th; + running++; + th_active = th; + active++; + } else if (th->state == TH_ST_COOLING || th->state == TH_ST_WARMING) { + th_active = th; + active++; + } + } + + cpu->nth_running = running; + cpu->nth_active = active; + cpu->th_running = th_running; + cpu->th_active = th_active; + + /* Update nth_running number in the channel */ + struct cpu_chan *chan = &cpu->chan; + if (chan_set(&chan->nth_running, value_int64(cpu->nth_running)) != 0) { + err("update_cpu: chan_set failed\n"); + return -1; + } + + return 0; +} + +/* Add the given thread to the list of threads assigned to the CPU */ +int +cpu_add_thread(struct cpu *cpu, struct thread *thread) +{ + if (find_thread(cpu, thread) != NULL) { + err("cpu_add_thread: thread %d already assigned to %s\n", + thread->tid, cpu->name); + return -1; + } + + DL_APPEND2(cpu->thread, thread, cpu_prev, cpu_next); + cpu->nthreads++; + + update_cpu(cpu); + return 0; +} diff --git a/src/emu/cpu.h b/src/emu/cpu.h new file mode 100644 index 0000000..63df1e4 --- /dev/null +++ b/src/emu/cpu.h @@ -0,0 +1,53 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef CPU_H +#define CPU_H + +struct cpu; + +#include "loom.h" +#include "thread.h" +#include "chan.h" + +struct cpu_chan { + struct chan pid_running; + struct chan tid_running; + struct chan nth_running; +}; + +struct cpu { + size_t gindex; /* In the system */ + char name[PATH_MAX]; + + /* Logical index: 0 to ncpus - 1 */ + int i; + + /* Physical id: as reported by lscpu(1) */ + int phyid; + + size_t nthreads; + size_t nth_running; + size_t nth_active; + struct thread *thread; /* List of threads assigned to this CPU */ + struct thread *th_running; /* One */ + struct thread *th_active; + + int is_virtual; + + /* Global list */ + struct cpu *next; + struct cpu *prev; + + /* Channels */ + struct cpu_chan chan; + + //struct model_ctx ctx; +}; + +void cpu_init(struct cpu *cpu, int i, int phyid, int is_virtual); +void cpu_set_gindex(struct cpu *cpu, int64_t gindex); +void cpu_set_name(struct cpu *cpu, int64_t loom); +int cpu_add_thread(struct cpu *cpu, struct thread *thread); + +#endif /* CPU_H */ diff --git a/src/emu/emu.c b/src/emu/emu.c index b847855..aebe2b8 100644 --- a/src/emu/emu.c +++ b/src/emu/emu.c @@ -6,26 +6,7 @@ #include "emu.h" #include - -int -emu_model_register(struct emu *emu, struct model_spec *spec, void *ctx) -{ - emu->model_ctx[spec->model] = ctx; - emu->model[spec->model] = spec; - return 0; -} - -void * -emu_model_get_context(struct emu *emu, struct model_spec *spec, int model) -{ - for (int i = 0; spec->depends[i]; i++) { - if (spec->depends[i] == model) - return emu->model_ctx[model]; - } - - /* Not allowed */ - return NULL; -} +#include "ovni/ovni_model.h" int emu_init(struct emu *emu, int argc, char *argv[]) @@ -54,8 +35,26 @@ emu_init(struct emu *emu, int argc, char *argv[]) return -1; } + /* Initialize the bay */ + bay_init(&emu->bay); + /* Register all the models */ - //emu_model_register(emu, ovni_model_spec, ctx); + emu_model_register(&emu->model, &ovni_model_spec, emu); + + if (ovni_model_spec.probe(emu) != 0) { + err("emu_init: ovni probe failed\n"); + return -1; + } + + if (ovni_model_spec.create(emu) != 0) { + err("emu_init: ovni create failed\n"); + return -1; + } + + if (ovni_model_spec.connect(emu) != 0) { + err("emu_init: ovni connect failed\n"); + return -1; + } return 0; } @@ -75,6 +74,17 @@ emu_step(struct emu *emu) return -1; } + emu->ev = emu_player_ev(&emu->player); + emu->stream = emu_player_stream(&emu->player); + emu->thread = emu_system_get_thread(emu->stream); + emu->proc = emu->thread->proc; + emu->loom = emu->proc->loom; + /* Otherwise progress */ + if (ovni_model_spec.event(emu) != 0) { + err("emu_init: ovni event failed\n"); + return -1; + } + return 0; } diff --git a/src/emu/emu.h b/src/emu/emu.h index 6e59b14..0cc9ac8 100644 --- a/src/emu/emu.h +++ b/src/emu/emu.h @@ -4,48 +4,47 @@ #ifndef EMU_H #define EMU_H +struct emu; + #include "bay.h" #include "pvtrace.h" #include "emu_trace.h" #include "emu_args.h" #include "emu_system.h" #include "emu_player.h" +#include "emu_model.h" +#include "emu_ev.h" enum error_values { ST_BAD = 666, ST_TOO_MANY_TH = 777, }; -struct emu; - -typedef int (emu_hook_t)(struct emu *emu); - -struct model_spec { - char *name; - int model; - char *depends; - emu_hook_t *probe; - emu_hook_t *create; - emu_hook_t *connect; - emu_hook_t *event; -}; - struct emu { - struct bay *bay; + struct bay bay; struct pvman *pvman; struct emu_args args; struct emu_trace trace; struct emu_system system; struct emu_player player; + struct emu_model model; - struct model_spec *model[256]; - void *model_ctx[256]; + /* Quick access */ + struct emu_stream *stream; + struct emu_ev *ev; + struct emu_thread *thread; + struct emu_proc *proc; + struct emu_loom *loom; }; int emu_init(struct emu *emu, int argc, char *argv[]); int emu_step(struct emu *emu); -int emu_model_register(struct emu *emu, struct model_spec *spec, void *ctx); -void *emu_model_get_context(struct emu *emu, struct model_spec *spec, int model); + +static inline struct emu * +emu_get(void *ptr) +{ + return (struct emu *) ptr; +} #endif /* EMU_H */ diff --git a/src/emu/emu_ev.c b/src/emu/emu_ev.c new file mode 100644 index 0000000..77e3a76 --- /dev/null +++ b/src/emu/emu_ev.c @@ -0,0 +1,31 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "emu_ev.h" + +void +emu_ev(struct emu_ev *ev, const struct ovni_ev *oev, + int64_t sclock, int64_t dclock) +{ + memset(ev, 0, sizeof(struct emu_ev)); + + ev->mcv[0] = ev->m = oev->header.model; + ev->mcv[1] = ev->c = oev->header.category; + ev->mcv[2] = ev->v = oev->header.value; + ev->mcv[3] = '\0'; + + ev->rclock = oev->header.clock; + ev->sclock = sclock; + ev->dclock = dclock; + + ev->payload_size = ovni_payload_size(oev); + + if (ev->payload_size > 0) { + ev->has_payload = 1; + ev->payload = &oev->payload; + + if (oev->header.flags & OVNI_EV_JUMBO) { + ev->is_jumbo = 1; + } + } +} diff --git a/src/emu/emu_ev.h b/src/emu/emu_ev.h new file mode 100644 index 0000000..5b9c8cb --- /dev/null +++ b/src/emu/emu_ev.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef EMU_EV_H +#define EMU_EV_H + +#include "ovni.h" +#include + +/* Easier to parse emulation event */ +struct emu_ev { + char m; + char c; + char v; + char mcv[4]; + + int64_t rclock; /* As-is clock in the binary stream */ + int64_t sclock; /* Corrected clock with stream offset */ + int64_t dclock; /* Delta corrected clock with stream offset */ + + int has_payload; + size_t payload_size; + int is_jumbo; + const union ovni_ev_payload *payload; /* NULL if no payload */ +}; + +void emu_ev(struct emu_ev *ev, const struct ovni_ev *oev, int64_t sclock, int64_t dclock); + +#endif /* EMU_EV_H */ diff --git a/src/emu/emu_model.c b/src/emu/emu_model.c new file mode 100644 index 0000000..1345c99 --- /dev/null +++ b/src/emu/emu_model.c @@ -0,0 +1,27 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "emu_model.h" + +#include + +int +emu_model_register(struct emu_model *model, struct model_spec *spec, void *ctx) +{ + int i = spec->model; + model->ctx[i] = ctx; + model->spec[i] = spec; + return 0; +} + +void * +emu_model_get_context(struct emu_model *model, struct model_spec *spec, int imodel) +{ + for (int i = 0; spec->depends[i]; i++) { + if (spec->depends[i] == imodel) + return model->ctx[imodel]; + } + + /* Not allowed */ + return NULL; +} diff --git a/src/emu/emu_model.h b/src/emu/emu_model.h new file mode 100644 index 0000000..fd339c3 --- /dev/null +++ b/src/emu/emu_model.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef EMU_MODEL_H +#define EMU_MODEL_H + +typedef int (emu_hook_t)(void *ptr); + +struct model_spec { + char *name; + int model; + char *depends; + emu_hook_t *probe; + emu_hook_t *create; + emu_hook_t *connect; + emu_hook_t *event; +}; + +struct emu_model { + struct model_spec *spec[256]; + void *ctx[256]; +}; + +int emu_model_parse(struct emu_model *model, struct model_spec *spec, void *ctx); +int emu_model_register(struct emu_model *model, struct model_spec *spec, void *ctx); +int emu_model_register(struct emu_model *model, struct model_spec *spec, void *ctx); +void *emu_model_get_context(struct emu_model *model, struct model_spec *spec, int imodel); + +#endif /* EMU_MODEL_H */ diff --git a/src/emu/emu_player.c b/src/emu/emu_player.c index f1496fb..5b46164 100644 --- a/src/emu/emu_player.c +++ b/src/emu/emu_player.c @@ -79,31 +79,9 @@ emu_player_init(struct emu_player *player, struct emu_trace *trace) return 0; } -/* Returns -1 on error, +1 if there are no more events and 0 if next event - * loaded properly */ -int -emu_player_step(struct emu_player *player) +static int +update_clocks(struct emu_player *player, struct emu_stream *stream) { - /* Add the stream back if still active */ - if (player->stream != NULL && step_stream(player, player->stream) < 0) { - err("player_step: step_stream() failed\n"); - return -1; - } - - /* Extract the next stream based on the lastclock */ - heap_node_t *node = heap_pop_max(&player->heap, stream_cmp); - - /* No more streams */ - if (node == NULL) - return +1; - - struct emu_stream *stream = heap_elem(node, struct emu_stream, hh); - - if (stream == NULL) { - err("player_step: heap_elem() returned NULL\n"); - return -1; - } - /* This can happen if two events are not ordered in the stream, but the * emulator picks other events in the middle. Example: * @@ -126,14 +104,64 @@ emu_player_step(struct emu_player *player) } if (sclock < player->lastclock) { - err("emu_player_step: backwards jump in time %ld -> %ld in stream '%s'\n", + err("backwards jump in time %ld -> %ld in stream '%s'\n", player->lastclock, sclock, stream->relpath); return -1; } - player->stream = stream; player->lastclock = sclock; player->deltaclock = player->lastclock - player->firstclock; return 0; } + +/* Returns -1 on error, +1 if there are no more events and 0 if next event + * loaded properly */ +int +emu_player_step(struct emu_player *player) +{ + /* Add the stream back if still active */ + if (player->stream != NULL && step_stream(player, player->stream) < 0) { + err("player_step: step_stream() failed\n"); + return -1; + } + + /* Extract the next stream based on the lastclock */ + heap_node_t *node = heap_pop_max(&player->heap, stream_cmp); + + /* No more streams */ + if (node == NULL) + return +1; + + struct emu_stream *stream = heap_elem(node, struct emu_stream, hh); + + if (stream == NULL) { + err("emu_player_step: heap_elem() returned NULL\n"); + return -1; + } + + if (update_clocks(player, stream) != 0) { + err("emu_player_step: update_clocks() failed\n"); + return -1; + } + + player->stream = stream; + + struct ovni_ev *oev = emu_stream_ev(stream); + int64_t sclock = emu_stream_evclock(stream, oev); + emu_ev(&player->ev, oev, sclock, player->deltaclock); + + return 0; +} + +struct emu_ev * +emu_player_ev(struct emu_player *player) +{ + return &player->ev; +} + +struct emu_stream * +emu_player_stream(struct emu_player *player) +{ + return player->stream; +} diff --git a/src/emu/emu_player.h b/src/emu/emu_player.h index a701755..5d98525 100644 --- a/src/emu/emu_player.h +++ b/src/emu/emu_player.h @@ -5,6 +5,7 @@ #define EMU_PLAYER_H #include "emu_trace.h" +#include "emu_ev.h" #include @@ -15,11 +16,12 @@ struct emu_player { int64_t deltaclock; int first_event; struct emu_stream *stream; + struct emu_ev ev; }; int emu_player_init(struct emu_player *player, struct emu_trace *trace); int emu_player_step(struct emu_player *player); -void emu_player_ev(struct emu_player *player, struct ovni_ev *ev); -void emu_player_stream(struct emu_player *player, struct emu_stream *stream); +struct emu_ev *emu_player_ev(struct emu_player *player); +struct emu_stream *emu_player_stream(struct emu_player *player); #endif /* EMU_PLAYER_H */ diff --git a/src/emu/emu_system.c b/src/emu/emu_system.c index 9d52802..5d9cdd4 100644 --- a/src/emu/emu_system.c +++ b/src/emu/emu_system.c @@ -337,7 +337,7 @@ init_global_cpus_list(struct emu_system *sys) { for (struct emu_loom *l = sys->looms; l; l = l->next) { for (size_t i = 0; i < l->ncpus; i++) { - struct emu_cpu *cpu = &l->cpu[i]; + struct cpu *cpu = &l->cpu[i]; DL_APPEND2(sys->cpus, cpu, prev, next); } @@ -371,14 +371,14 @@ print_system(struct emu_system *sys) } err("- cpus:\n"); for (size_t i = 0; i < l->ncpus; i++) { - struct emu_cpu *cpu = &l->cpu[i]; + struct cpu *cpu = &l->cpu[i]; err(" %s\n", cpu->name); err(" - i %d\n", cpu->i); err(" - phyid %d\n", cpu->phyid); err(" - gindex %ld\n", cpu->gindex); } - struct emu_cpu *cpu = &l->vcpu; + struct cpu *cpu = &l->vcpu; err("- %s\n", cpu->name); err(" - i %d\n", cpu->i); err(" - phyid %d\n", cpu->phyid); @@ -521,27 +521,17 @@ has_cpus_array(JSON_Value *metadata) } static int -add_new_cpu(struct emu_loom *loom, int i, int phyid) +add_new_cpu(struct emu_loom *loom, int i, int phyid, int ncpus) { - struct emu_cpu *cpu = &loom->cpu[i]; + struct cpu *cpu = &loom->cpu[i]; - if (i < 0 || i >= (int) loom->ncpus) { + if (i < 0 || i >= ncpus) { err("add_new_cpu: new CPU i=%d out of bounds in %s\n", i, loom->relpath); return -1; } - if (cpu->state != CPU_ST_UNKNOWN) { - die("add_new_cpu: new CPU i=%d in unexpected in %s\n", - i, loom->relpath); - return -1; - } - - cpu->state = CPU_ST_READY; - cpu->i = i; - cpu->phyid = phyid; - cpu->loom = loom; - cpu->is_virtual = 0; + cpu_init(cpu, loom, i, phyid, 0); return 0; } @@ -571,8 +561,7 @@ load_proc_cpus(struct emu_proc *proc) } struct emu_loom *loom = proc->loom; - loom->ncpus = ncpus; - loom->cpu = calloc(ncpus, sizeof(struct emu_cpu)); + struct cpu *cpus = calloc(ncpus, sizeof(struct cpu)); if (loom->cpu == NULL) { err("load_proc_cpus: calloc failed: %s\n", strerror(errno)); @@ -596,30 +585,11 @@ load_proc_cpus(struct emu_proc *proc) } } - return 0; -} - -static int -init_virtual_cpu(struct emu_loom *loom) -{ - struct emu_cpu *vcpu = &loom->vcpu; - - if (vcpu->state != CPU_ST_UNKNOWN) { - err("init_virtual_cpu: unexpected virtual CPU state in %s\n", - loom->relpath); - return -1; - } - - vcpu->state = CPU_ST_READY; - vcpu->i = -1; - vcpu->phyid = -1; - vcpu->loom = loom; - vcpu->is_virtual = 1; + loom_set_cpus(loom, cpus, ncpus); return 0; } - static int load_loom_cpus(struct emu_loom *loom) { @@ -661,6 +631,9 @@ load_loom_cpus(struct emu_loom *loom) return -1; } + /* Init virtual CPU too */ + cpu_init(&l->vcpu, l, -1, -1, 1); + return 0; } @@ -673,11 +646,6 @@ init_cpus(struct emu_system *sys) err("init_cpus: load_loom_cpus() failed\n"); return -1; } - - if (init_virtual_cpu(l) != 0) { - err("init_cpus: init_virtual_cpu() failed\n"); - return -1; - } } return 0; @@ -688,7 +656,7 @@ init_global_indices(struct emu_system *sys) { size_t iloom = 0; for (struct emu_loom *l = sys->looms; l; l = l->next) - l->gindex = iloom++; + loom_set_gindex(l, iloom++); sys->nprocs = 0; for (struct emu_proc *p = sys->procs; p; p = p->gnext) @@ -699,12 +667,12 @@ init_global_indices(struct emu_system *sys) t->gindex = sys->nprocs++; sys->ncpus = 0; - for (struct emu_cpu *c = sys->cpus; c; c = c->next) - c->gindex = sys->ncpus++; + for (struct cpu *c = sys->cpus; c; c = c->next) + cpu_set_gindex(c, sys->ncpus++); } static int -init_cpu_name(struct emu_cpu *cpu) +init_cpu_name(struct cpu *cpu) { size_t i = cpu->loom->gindex; size_t j = cpu->i; @@ -726,7 +694,7 @@ init_cpu_name(struct emu_cpu *cpu) static int init_cpu_names(struct emu_system *sys) { - for (struct emu_cpu *cpu = sys->cpus; cpu; cpu = cpu->next) { + for (struct cpu *cpu = sys->cpus; cpu; cpu = cpu->next) { if (init_cpu_name(cpu) != 0) return -1; } diff --git a/src/emu/emu_system.h b/src/emu/emu_system.h index 2995c00..4fa5bca 100644 --- a/src/emu/emu_system.h +++ b/src/emu/emu_system.h @@ -20,50 +20,12 @@ struct emu_proc; struct emu_loom; struct emu_system; -enum emu_cpu_state { - CPU_ST_UNKNOWN, - CPU_ST_READY, -}; - #define MAX_MODELS 256 struct model_ctx { void *data[MAX_MODELS]; }; -struct emu_cpu { - size_t gindex; /* In the system */ - char name[PATH_MAX]; - - /* Logical index: 0 to ncpus - 1 */ - int i; - - /* Physical id: as reported by lscpu(1) */ - int phyid; - - enum emu_cpu_state state; - - /* The loom of the CPU */ - struct emu_loom *loom; - - size_t nthreads; - struct emu_thread *thread; /* List of threads assigned to this CPU */ - - size_t nrunning_threads; - struct emu_thread *th_running; /* One */ - - size_t nactive_threads; - struct emu_thread *th_active; - - int is_virtual; - - /* Global list */ - struct emu_cpu *next; - struct emu_cpu *prev; - - struct model_ctx ctx; -}; - /* Emulated thread runtime status */ enum emu_thread_state { TH_ST_UNKNOWN, @@ -161,7 +123,7 @@ struct emu_loom { int64_t clock_offset; /* Virtual CPU */ - struct emu_cpu vcpu; + struct emu_cpu *vcpu; /* Local list */ size_t nprocs; @@ -198,6 +160,8 @@ struct emu_system { }; int emu_system_init(struct emu_system *sys, struct emu_args *args, struct emu_trace *trace); +struct emu_thread *emu_system_get_thread(struct emu_stream *stream); +struct emu_cpu *emu_system_find_cpu(struct emu_loom *loom, int cpuid); int model_ctx_set(struct model_ctx *ctx, int model, void *data); int model_ctx_get(struct model_ctx *ctx, int model, void *data); diff --git a/src/emu/emu_system_thread.c b/src/emu/emu_system_thread.c new file mode 100644 index 0000000..910fcfa --- /dev/null +++ b/src/emu/emu_system_thread.c @@ -0,0 +1,6 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "emu_system.h" + +#include "chan.h" diff --git a/src/emu/loom.c b/src/emu/loom.c new file mode 100644 index 0000000..7ebda48 --- /dev/null +++ b/src/emu/loom.c @@ -0,0 +1,39 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "loom.h" + +void +loom_init(struct loom *loom) +{ + memset(loom, 0, sizeof(struct loom)); +} + +void +loom_set_gindex(struct loom *loom, int64_t gindex) +{ + loom->gindex = gindex; +} + +void +loom_set_cpus(struct loom *loom, struct cpu *cpus, size_t ncpus) +{ + loom->ncpus = ncpus; + loom->cpu = cpus; +} + +void +loom_set_vcpu(struct loom *loom, struct cpu *vcpu) +{ + loom->vcpu = vcpu; +} + +struct emu_proc * +loom_find_proc(struct emu_loom *loom, pid_t pid) +{ + for (struct emu_proc *proc = loom->procs; proc; proc = proc->lnext) { + if (proc->pid == pid) + return proc; + } + return NULL; +} diff --git a/src/emu/loom.h b/src/emu/loom.h new file mode 100644 index 0000000..97cdd7f --- /dev/null +++ b/src/emu/loom.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef LOOM_H +#define LOOM_H + +struct loom; + +#include +#include +#include + +struct loom { + size_t gindex; + + char name[PATH_MAX]; /* Loom directory name */ + char path[PATH_MAX]; + char relpath[PATH_MAX]; /* Relative to tracedir */ + char hostname[PATH_MAX]; + + size_t max_ncpus; + size_t max_phyid; + size_t ncpus; + size_t offset_ncpus; + struct cpu *cpu; + int rank_enabled; + + int64_t clock_offset; + + /* Virtual CPU */ + struct cpu *vcpu; + + /* Local list */ + size_t nprocs; + struct proc *procs; + + /* Global list */ + struct loom *next; + struct loom *prev; + + //struct model_ctx ctx; +}; + +void loom_init(struct loom *loom); + +#endif /* LOOM_H */ diff --git a/src/emu/nanos6/nanos6_model.h b/src/emu/nanos6/nanos6_model.h new file mode 100644 index 0000000..c898d59 --- /dev/null +++ b/src/emu/nanos6/nanos6_model.h @@ -0,0 +1,69 @@ +/* Copyright (c) 2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef OVNI_MODEL_H +#define OVNI_MODEL_H + +#include "ovni/ovni_model.h" +#include "chan.h" + +/* The values of nanos6_ss_state are synced to the previous + * CTF implementation. */ +enum nanos6_ss_state { + ST_NANOS6_TASK_BODY = 1, + ST_NANOS6_TASK_CREATING, + ST_NANOS6_TASK_SUBMIT, + ST_NANOS6_TASK_SPAWNING, + ST_NANOS6_TASK_FOR, + ST_NANOS6_SCHED_ADDING, + ST_NANOS6_SCHED_PROCESSING, + ST_NANOS6_SCHED_SERVING, + ST_NANOS6_DEP_REG, + ST_NANOS6_DEP_UNREG, + ST_NANOS6_BLK_TASKWAIT, + ST_NANOS6_BLK_WAITFOR, + ST_NANOS6_BLK_BLOCKING, + ST_NANOS6_BLK_UNBLOCKING, + ST_NANOS6_ALLOCATING, + ST_NANOS6_FREEING, + ST_NANOS6_HANDLING_TASK, + ST_NANOS6_WORKER_LOOP, + ST_NANOS6_SWITCH_TO, + ST_NANOS6_MIGRATE, + ST_NANOS6_SUSPEND, + ST_NANOS6_RESUME, + + /* Value 51 is broken in old Paraver */ + EV_NANOS6_SCHED_RECV = 60, + EV_NANOS6_SCHED_SEND, + EV_NANOS6_SCHED_SELF, + EV_NANOS6_CPU_IDLE, + EV_NANOS6_CPU_ACTIVE, + EV_NANOS6_SIGNAL, +}; + +enum nanos6_thread_type { + ST_NANOS6_TH_LEADER = 1, + ST_NANOS6_TH_MAIN = 2, + ST_NANOS6_TH_WORKER = 3, + ST_NANOS6_TH_EXTERNAL = 4, +}; + +struct nanos6_thread { + struct task_stack nanos6_task_stack; +}; + +struct nanos6_proc { + struct task_info nanos6_task_info; +}; + +struct nanos6_emu { + struct ovni_emu *ovni; +}; + +int nanos6_model_probe(struct emu *emu); +int nanos6_model_create(struct emu *emu); +int nanos6_model_connect(struct emu *emu); +int nanos6_model_event(struct emu *emu); + +#endif /* OVNI_MODEL_H */ diff --git a/src/emu/old_emu.c b/src/emu/old_emu.c new file mode 100644 index 0000000..84f73f4 --- /dev/null +++ b/src/emu/old_emu.c @@ -0,0 +1,1250 @@ +/* Copyright (c) 2021-2022 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#define _POSIX_C_SOURCE 200112L + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include "chan.h" +#include "emu.h" +#include "ovni.h" +#include "pcf.h" +#include "prv.h" +#include "trace.h" +#include "utlist.h" + +/* Obtains the corrected clock of the given event */ +static int64_t +evclock(struct ovni_stream *stream, struct ovni_ev *ev) +{ + return (int64_t) ovni_ev_get_clock(ev) + stream->clock_offset; +} + +static void +print_ev(struct ovni_stream *stream, struct ovni_ev *ev) +{ + int64_t clock = evclock(stream, ev); + + int64_t delta = clock - stream->lastclock; + UNUSED(delta); + + dbg(">>> %s.%d.%d %c %c %c % 20ld % 15ld ", + stream->loom->hostname, + stream->proc->pid, + stream->thread->tid, + ev->header.model, ev->header.category, ev->header.value, clock, delta); + + int payloadsize = ovni_payload_size(ev); + for (int i = 0; i < payloadsize; i++) { + dbg("%d ", ev->payload.u8[i]); + } + dbg("\n"); +} + +static void +print_cur_ev(struct ovni_emu *emu) +{ + print_ev(emu->cur_stream, emu->cur_ev); +} + +/* Update the tracking channel if needed */ +static void +cpu_update_tracking_chan(struct ovni_chan *cpu_chan, struct ovni_ethread *th) +{ + int cpu_enabled = 0; + + switch (cpu_chan->track) { + case CHAN_TRACK_TH_RUNNING: + cpu_enabled = th->is_running; + break; + case CHAN_TRACK_TH_ACTIVE: + cpu_enabled = th->is_active; + break; + default: + dbg("ignoring thread %d chan %d with track=%d\n", + th->tid, cpu_chan->id, cpu_chan->track); + return; + } + + struct ovni_chan *th_chan = &th->chan[cpu_chan->id]; + int th_enabled = chan_is_enabled(th_chan); + + /* Enable the cpu channel if needed */ + if (cpu_enabled && !chan_is_enabled(cpu_chan)) + chan_enable(cpu_chan, cpu_enabled); + + /* Copy the state from the thread channel if needed */ + if (th_enabled && cpu_enabled) { + /* Both enabled: simply follow the same value */ + chan_copy(cpu_chan, th_chan); + } else if (th_enabled && !cpu_enabled) { + /* Only thread enabled: disable CPU */ + if (chan_is_enabled(cpu_chan)) + chan_disable(cpu_chan); + } else if (!th_enabled && cpu_enabled) { + /* Only CPU enabled: is this possible? Set to bad */ + chan_set(cpu_chan, ST_BAD); + err("warning: cpu %s chan %d enabled but tracked thread %d chan disabled\n", + cpu_chan->cpu->name, + cpu_chan->id, + th->tid); + } else { + /* Both disabled: disable CPU channel if needed */ + if (chan_is_enabled(cpu_chan)) + chan_disable(cpu_chan); + } + + dbg("cpu %s chan %d enabled=%d state=%d\n", + cpu_chan->cpu->name, cpu_chan->id, + chan_is_enabled(cpu_chan), + chan_get_st(cpu_chan)); +} + +void +emu_cpu_update_chan(struct ovni_cpu *cpu, struct ovni_chan *cpu_chan) +{ + int count = 0; + struct ovni_ethread *th = NULL; + + /* Determine the source of tracking */ + + switch (cpu_chan->track) { + case CHAN_TRACK_TH_RUNNING: + count = cpu->nrunning_threads; + th = cpu->th_running; + break; + case CHAN_TRACK_TH_ACTIVE: + count = cpu->nactive_threads; + th = cpu->th_active; + break; + default: + dbg("ignoring %s chan %d with track=%d\n", + cpu->name, cpu_chan->id, cpu_chan->track); + return; + } + + /* Based on how many threads, determine the state */ + if (count == 0) { + /* The channel can be already disabled (migration of paused + * thread) so only disable it if needed. */ + if (chan_is_enabled(cpu_chan)) + chan_disable(cpu_chan); + } else if (count == 1) { + if (th == NULL) + die("emu_cpu_update_chan: tracking thread is NULL\n"); + + /* A unique thread found: copy the state */ + dbg("cpu_update_chan: unique thread %d found, updating chan %d\n", + th->tid, cpu_chan->id); + + cpu_update_tracking_chan(cpu_chan, th); + } else { + /* More than one thread: enable the channel and set it to a + * error value */ + if (!chan_is_enabled(cpu_chan)) + chan_enable(cpu_chan, 1); + + if (chan_get_st(cpu_chan) != ST_TOO_MANY_TH) + chan_set(cpu_chan, ST_TOO_MANY_TH); + } +} + +static void +propagate_channels(struct ovni_emu *emu) +{ + struct ovni_chan *th_chan = NULL; + struct ovni_chan *tmp = NULL; + + /* Only propagate thread channels to their corresponding CPU */ + + DL_FOREACH_SAFE(emu->th_chan, th_chan, tmp) + { + if (th_chan->thread == NULL) + die("propagate_channels: channel thread is NULL\n"); + + struct ovni_ethread *thread = th_chan->thread; + + /* No CPU in the thread */ + if (thread->cpu == NULL) + continue; + + struct ovni_cpu *cpu = thread->cpu; + + struct ovni_chan *cpu_chan = &cpu->chan[th_chan->id]; + + dbg("propagate thread %d chan %d in cpu %s\n", + thread->tid, th_chan->id, cpu->name); + + emu_cpu_update_chan(cpu, cpu_chan); + } +} + +static void +emit_channels(struct ovni_emu *emu) +{ + struct ovni_chan *cpu_chan, *th_chan; + + dbg("emu enters emit_channels -------------------\n"); + + /* Emit only the channels that have been updated. We need a list + * of updated channels */ + DL_FOREACH(emu->th_chan, th_chan) + { + dbg("emu emits th chan %d\n", th_chan->id); + chan_emit(th_chan); + } + + DL_FOREACH(emu->cpu_chan, cpu_chan) + { + dbg("emu emits cpu chan %d\n", cpu_chan->id); + chan_emit(cpu_chan); + } + + /* Reset the list of updated channels */ + dbg("emu resets the list of dirty channels -------------------\n"); + emu->th_chan = NULL; + emu->cpu_chan = NULL; +} + +static void +hook_init(struct ovni_emu *emu) +{ + hook_init_ovni(emu); + hook_init_nosv(emu); + hook_init_tampi(emu); + hook_init_openmp(emu); + hook_init_nodes(emu); + hook_init_kernel(emu); + hook_init_nanos6(emu); +} + +static void +hook_end(struct ovni_emu *emu) +{ + hook_end_nosv(emu); + hook_end_nanos6(emu); +} + +static void +hook_pre(struct ovni_emu *emu) +{ + switch (emu->cur_ev->header.model) { + case 'O': + hook_pre_ovni(emu); + break; + case 'V': + hook_pre_nosv(emu); + break; + case 'T': + hook_pre_tampi(emu); + break; + case 'M': + hook_pre_openmp(emu); + break; + case 'D': + hook_pre_nodes(emu); + break; + case 'K': + hook_pre_kernel(emu); + break; + case '6': + hook_pre_nanos6(emu); + break; + default: + break; + } +} + +static void +hook_post(struct ovni_emu *emu) +{ + switch (emu->cur_ev->header.model) { + default: + // dbg("unknown model %c\n", emu->cur_ev->model); + break; + } +} + +static void +set_current(struct ovni_emu *emu, struct ovni_stream *stream) +{ + emu->cur_stream = stream; + emu->cur_ev = stream->cur_ev; + emu->cur_loom = stream->loom; + emu->cur_proc = stream->proc; + emu->cur_thread = stream->thread; +} + +/* + * heap_node_compare_t - comparison function, returns: + + * > 0 if a > b + * < 0 if a < b + * = 0 if a == b + * + * Invert the comparison function to get a min-heap instead + */ +static inline int +stream_cmp(heap_node_t *a, heap_node_t *b) +{ + struct ovni_stream *sa, *sb; + + sa = heap_elem(a, struct ovni_stream, hh); + sb = heap_elem(b, struct ovni_stream, hh); + + if (sb->lastclock < sa->lastclock) + return -1; + else if (sb->lastclock > sa->lastclock) + return +1; + else + return 0; +} + +static double +get_time(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (double) ts.tv_sec + (double) ts.tv_nsec * 1.0e-9; +} + +static void +print_progress(struct ovni_emu *emu) +{ + double cpu_written = (double) ftell(emu->prv_cpu); + double th_written = (double) ftell(emu->prv_thread); + + double written = cpu_written + th_written; + + double progress = ((double) emu->global_offset) + / ((double) emu->global_size); + + double time_now = get_time(); + double time_elapsed = time_now - emu->start_emulation_time; + double time_total = time_elapsed / progress; + double time_left = time_total - time_elapsed; + + double speed_in = (double) emu->nev_processed / time_elapsed; + double speed_out = written / time_elapsed; + + int tmin = (int) (time_left / 60.0); + int sec = (int) ((time_left / 60.0 - tmin) * 60); + + err("%.1f%% done at %.0f Kev/s, out %.1f GB CPU / %.1f GB TH at %.1f MB/s (%d min %d s left)\n", + 100.0 * progress, + speed_in / 1024.0, + cpu_written / (1024.0 * 1024.0 * 1024.0), + th_written / (1024.0 * 1024.0 * 1024.0), + speed_out / (1024.0 * 1024), + tmin, sec); +} + +/* Loads the next event and sets the lastclock in the stream. + * Returns -1 if the stream has no more events. */ +static int +emu_step_stream(struct ovni_emu *emu, struct ovni_stream *stream) +{ + if (ovni_load_next_event(stream) < 0) + return -1; + + stream->lastclock = evclock(stream, stream->cur_ev); + + heap_insert(&emu->sorted_stream, &stream->hh, &stream_cmp); + + return 0; +} + +static int +next_event(struct ovni_emu *emu) +{ + static int done_first = 0; + + /* Extract the next stream based on the event clock */ + heap_node_t *node = heap_pop_max(&emu->sorted_stream, stream_cmp); + + /* No more streams */ + if (node == NULL) + return -1; + + struct ovni_stream *stream = heap_elem(node, struct ovni_stream, hh); + + if (stream == NULL) + die("next_event: heap_elem returned NULL\n"); + + set_current(emu, stream); + + emu->global_offset += ovni_ev_size(stream->cur_ev); + + // err("stream %d clock at %ld\n", stream->tid, stream->lastclock); + + /* This can happen if two events are not ordered in the stream, but the + * emulator picks other events in the middle. Example: + * + * Stream A: 10 3 ... + * Stream B: 5 12 + * + * emulator output: + * 5 + * 10 + * 3 -> warning! + * 12 + * ... + * */ + if (emu->lastclock > stream->lastclock) { + err("warning: backwards jump in time %lu -> %lu for tid %d\n", + emu->lastclock, stream->lastclock, stream->tid); + + if (emu->enable_linter) + abort(); + } + + emu->lastclock = stream->lastclock; + + if (!done_first) { + done_first = 1; + emu->firstclock = emu->lastclock; + } + + emu->delta_time = emu->lastclock - emu->firstclock; + + return 0; +} + +static void +emu_load_first_events(struct ovni_emu *emu) +{ + /* Prepare the stream heap */ + heap_init(&emu->sorted_stream); + + emu->lastclock = 0; + + /* Load initial streams and events */ + struct ovni_trace *trace = &emu->trace; + for (size_t i = 0; i < trace->nstreams; i++) { + struct ovni_stream *stream = &trace->stream[i]; + emu->global_size += stream->size; + + if (emu_step_stream(emu, stream) < 0) { + err("warning: empty stream for tid %d\n", stream->tid); + + if (emu->enable_linter) + abort(); + + continue; + } + } +} + +void +emu_run(struct ovni_emu *emu) +{ + emu->nev_processed = 0; + emu_load_first_events(emu); + emu->start_emulation_time = get_time(); + hook_init(emu); + emit_channels(emu); + + /* Then process all events */ + for (size_t i = 0; next_event(emu) == 0; i++) { + print_cur_ev(emu); + + hook_pre(emu); + + propagate_channels(emu); + emit_channels(emu); + + hook_post(emu); + + if (i >= 100000) { + print_progress(emu); + i = 0; + } + + emu->nev_processed++; + + emu_step_stream(emu, emu->cur_stream); + } + + hook_end(emu); + print_progress(emu); +} + +struct ovni_ethread * +emu_get_thread(struct ovni_eproc *proc, int tid) +{ + for (size_t i = 0; i < proc->nthreads; i++) { + struct ovni_ethread *thread = &proc->thread[i]; + if (thread->tid == tid) + return thread; + } + + return NULL; +} + +static void +add_new_cpu(struct ovni_emu *emu, struct ovni_loom *loom, int i, int phyid) +{ + struct ovni_cpu *cpu = &loom->cpu[i]; + + if (i < 0 || i >= (int) loom->ncpus) + die("CPU with index %d in loom %s is out of bounds\n", + i, loom->hostname); + + if (cpu->state != CPU_ST_UNKNOWN) + die("new cpu %d in unexpected state in loom %s\n", + i, loom->hostname); + + cpu->state = CPU_ST_READY; + cpu->i = i; + cpu->phyid = phyid; + cpu->gindex = emu->total_ncpus++; + cpu->loom = loom; + + dbg("new cpu %d at phyid=%d\n", cpu->gindex, phyid); +} + +static int +proc_load_cpus(struct ovni_emu *emu, struct ovni_loom *loom, + struct ovni_eproc *proc, + struct ovni_eproc *metadata_proc) +{ + JSON_Object *meta = json_value_get_object(proc->meta); + if (meta == NULL) + die("json_value_get_object() failed\n"); + + JSON_Array *cpuarray = json_object_get_array(meta, "cpus"); + + /* This process doesn't have the cpu list */ + if (cpuarray == NULL) + return -1; + + if (metadata_proc) + die("duplicated metadata for proc %d and %d in loom %s\n", + metadata_proc->pid, proc->pid, + loom->hostname); + + if (loom->ncpus != 0) + die("loom %s already has CPUs\n", loom->hostname); + + loom->ncpus = json_array_get_count(cpuarray); + + if (loom->ncpus == 0) + die("loom %s proc %d has metadata but no CPUs\n", + loom->hostname, proc->pid); + + loom->cpu = calloc(loom->ncpus, sizeof(struct ovni_cpu)); + + if (loom->cpu == NULL) + die("calloc failed: %s\n", strerror(errno)); + + for (size_t i = 0; i < loom->ncpus; i++) { + JSON_Object *cpu = json_array_get_object(cpuarray, i); + + if (cpu == NULL) + die("proc_load_cpus: json_array_get_object() failed\n"); + + int index = (int) json_object_get_number(cpu, "index"); + int phyid = (int) json_object_get_number(cpu, "phyid"); + + add_new_cpu(emu, loom, index, phyid); + } + + /* If we reach this point, all CPUs are in the ready state */ + + /* Init the vcpu as well */ + struct ovni_cpu *vcpu = &loom->vcpu; + if (vcpu->state != CPU_ST_UNKNOWN) + die("unexpected virtual CPU state in loom %s\n", + loom->hostname); + + vcpu->state = CPU_ST_READY; + vcpu->i = -1; + vcpu->phyid = -1; + vcpu->gindex = emu->total_ncpus++; + vcpu->loom = loom; + + dbg("new vcpu %d\n", vcpu->gindex); + + return 0; +} + +/* Obtain CPUs in the metadata files and other data */ +static void +load_metadata(struct ovni_emu *emu) +{ + struct ovni_trace *trace = &emu->trace; + + for (size_t i = 0; i < trace->nlooms; i++) { + struct ovni_loom *loom = &trace->loom[i]; + loom->offset_ncpus = emu->total_ncpus; + + struct ovni_eproc *metadata_proc = NULL; + + for (size_t j = 0; j < loom->nprocs; j++) { + struct ovni_eproc *proc = &loom->proc[j]; + + if (proc_load_cpus(emu, loom, proc, metadata_proc) < 0) + continue; + + if (metadata_proc) + die("duplicated metadata found in pid %d and %d\n", + metadata_proc->pid, + proc->pid); + + metadata_proc = proc; + } + + /* One of the process must have the list of CPUs */ + if (metadata_proc == NULL) + die("no metadata found in loom %s\n", loom->hostname); + + if (loom->ncpus == 0) + die("no CPUs found in loom %s\n", loom->hostname); + } +} + +static int +destroy_metadata(struct ovni_emu *emu) +{ + struct ovni_trace *trace = &emu->trace; + + for (size_t i = 0; i < trace->nlooms; i++) { + struct ovni_loom *loom = &trace->loom[i]; + for (size_t j = 0; j < loom->nprocs; j++) { + struct ovni_eproc *proc = &loom->proc[j]; + + if (proc->meta == NULL) + die("cannot destroy metadata: is NULL\n"); + + json_value_free(proc->meta); + } + + free(loom->cpu); + } + + return 0; +} + +static void +open_prvs(struct ovni_emu *emu, char *tracedir) +{ + char path[PATH_MAX]; + + sprintf(path, "%s/%s", tracedir, "thread.prv"); + + emu->prv_thread = fopen(path, "w"); + + if (emu->prv_thread == NULL) { + err("error opening thread PRV file %s: %s\n", path, + strerror(errno)); + exit(EXIT_FAILURE); + } + + sprintf(path, "%s/%s", tracedir, "cpu.prv"); + + emu->prv_cpu = fopen(path, "w"); + + if (emu->prv_cpu == NULL) { + err("error opening cpu PRV file %s: %s\n", path, + strerror(errno)); + exit(EXIT_FAILURE); + } + + prv_header(emu->prv_thread, emu->total_nthreads); + prv_header(emu->prv_cpu, emu->total_ncpus); +} + +static void +open_pcfs(struct ovni_emu *emu, char *tracedir) +{ + char path[PATH_MAX]; + + sprintf(path, "%s/%s", tracedir, "thread.pcf"); + pcf_open(&emu->pcf[CHAN_TH], path, CHAN_TH); + + sprintf(path, "%s/%s", tracedir, "cpu.pcf"); + pcf_open(&emu->pcf[CHAN_CPU], path, CHAN_CPU); +} + +/* Fix the trace duration at the end */ +static void +fix_prv_headers(struct ovni_emu *emu) +{ + prv_fix_header(emu->prv_thread, emu->delta_time, emu->total_nthreads); + prv_fix_header(emu->prv_cpu, emu->delta_time, emu->total_ncpus); +} + +static void +close_prvs(struct ovni_emu *emu) +{ + fclose(emu->prv_thread); + fclose(emu->prv_cpu); +} + +static void +close_pcfs(struct ovni_emu *emu) +{ + pcf_close(&emu->pcf[CHAN_TH]); + pcf_close(&emu->pcf[CHAN_CPU]); +} + +static void +usage(void) +{ + err("Usage: ovniemu [-c offsetfile] tracedir\n"); + err("\n"); + err("Options:\n"); + err(" -c offsetfile Use the given offset file to correct\n"); + err(" the clocks among nodes. It can be\n"); + err(" generated by the ovnisync program\n"); + err("\n"); + err(" tracedir The output trace dir generated by ovni.\n"); + err("\n"); + err("The output PRV files are placed in the tracedir directory.\n"); + + exit(EXIT_FAILURE); +} + +static void +parse_args(struct ovni_emu *emu, int argc, char *argv[]) +{ + int opt; + + while ((opt = getopt(argc, argv, "c:l")) != -1) { + switch (opt) { + case 'c': + emu->clock_offset_file = optarg; + break; + case 'l': + emu->enable_linter = 1; + break; + default: /* '?' */ + usage(); + } + } + + if (optind >= argc) { + err("missing tracedir\n"); + usage(); + } + + emu->tracedir = argv[optind]; +} + +static void +set_clock_offsets(struct ovni_emu *emu, const char *host, size_t offset) +{ + size_t matches = 0; + + for (size_t i = 0; i < emu->trace.nlooms; i++) { + struct ovni_loom *loom = &emu->trace.loom[i]; + + /* Match the hostname exactly */ + if (strcmp(loom->hostname, host) != 0) + continue; + + if (loom->clock_offset != 0) + die("loom %s already has a clock offset\n", loom->dname); + + loom->clock_offset = offset; + matches++; + } + + if (matches == 0) + die("no loom has hostname %s\n", host); +} + +static void +load_clock_offsets(struct ovni_emu *emu) +{ + FILE *f = NULL; + + if (emu->clock_offset_file != NULL) { + f = fopen(emu->clock_offset_file, "r"); + + /* If provided by the user, it must exist */ + if (f == NULL) { + err("error opening clock offset file %s: %s\n", + emu->clock_offset_file, + strerror(errno)); + exit(EXIT_FAILURE); + } + } else { + char path[PATH_MAX]; + if (snprintf(path, PATH_MAX, "%s/clock-offsets.txt", + emu->tracedir) + >= PATH_MAX) { + die("clock offset path too long\n"); + } + + f = fopen(path, "r"); + + if (f == NULL) { + /* May not exist, but is fine */ + return; + } + } + + /* Ignore header line */ + char buf[1024]; + if (fgets(buf, 1024, f) == NULL) { + err("missing header line in clock offset file"); + exit(EXIT_FAILURE); + } + + while (1) { + errno = 0; + int rank, ret; + double offset, mean, std; + char host[OVNI_MAX_HOSTNAME]; + ret = fscanf(f, "%d %s %lf %lf %lf", &rank, host, &offset, &mean, &std); + + if (ret == EOF) { + if (errno != 0) { + perror("fscanf failed"); + exit(EXIT_FAILURE); + } + + break; + } + + if (ret != 5) { + err("fscanf read %d instead of 5 fields in %s\n", + ret, emu->clock_offset_file); + exit(EXIT_FAILURE); + } + + set_clock_offsets(emu, host, (int64_t) offset); + } + + /* Then populate the stream offsets */ + + struct ovni_trace *trace = &emu->trace; + + for (size_t i = 0; i < trace->nstreams; i++) { + struct ovni_stream *stream = &trace->stream[i]; + struct ovni_loom *loom = stream->loom; + stream->clock_offset = loom->clock_offset; + } + + fclose(f); + + err("loaded clock offsets ok\n"); +} + +static void +write_row_cpu(struct ovni_emu *emu) +{ + char path[PATH_MAX]; + + sprintf(path, "%s/%s", emu->tracedir, "cpu.row"); + + FILE *f = fopen(path, "w"); + + if (f == NULL) { + perror("cannot open row file"); + exit(EXIT_FAILURE); + } + + fprintf(f, "LEVEL NODE SIZE 1\n"); + fprintf(f, "hostname\n"); + fprintf(f, "\n"); + + fprintf(f, "LEVEL THREAD SIZE %ld\n", emu->total_ncpus); + + for (size_t i = 0; i < emu->total_ncpus; i++) { + struct ovni_cpu *cpu = emu->global_cpu[i]; + fprintf(f, "%s\n", cpu->name); + } + + fclose(f); +} + +static void +write_row_thread(struct ovni_emu *emu) +{ + char path[PATH_MAX]; + + sprintf(path, "%s/%s", emu->tracedir, "thread.row"); + + FILE *f = fopen(path, "w"); + + if (f == NULL) { + perror("cannot open row file"); + exit(EXIT_FAILURE); + } + + fprintf(f, "LEVEL NODE SIZE 1\n"); + fprintf(f, "hostname\n"); + fprintf(f, "\n"); + + fprintf(f, "LEVEL THREAD SIZE %ld\n", emu->total_nthreads); + + for (size_t i = 0; i < emu->total_nthreads; i++) { + struct ovni_ethread *th = emu->global_thread[i]; + fprintf(f, "THREAD %d.%d\n", th->proc->appid, th->tid); + } + + fclose(f); +} + +static void +init_threads(struct ovni_emu *emu) +{ + emu->total_nthreads = 0; + emu->total_nprocs = 0; + + struct ovni_trace *trace = &emu->trace; + + /* Count total processes and threads */ + for (size_t i = 0; i < trace->nlooms; i++) { + struct ovni_loom *loom = &trace->loom[i]; + for (size_t j = 0; j < loom->nprocs; j++) { + struct ovni_eproc *proc = &loom->proc[j]; + emu->total_nprocs++; + for (size_t k = 0; k < proc->nthreads; k++) { + emu->total_nthreads++; + } + } + } + + emu->global_thread = calloc(emu->total_nthreads, + sizeof(*emu->global_thread)); + + if (emu->global_thread == NULL) { + perror("calloc failed"); + exit(EXIT_FAILURE); + } + + int gi = 0; + + /* Populate global_thread array */ + for (size_t i = 0; i < trace->nlooms; i++) { + struct ovni_loom *loom = &trace->loom[i]; + for (size_t j = 0; j < loom->nprocs; j++) { + struct ovni_eproc *proc = &loom->proc[j]; + for (size_t k = 0; k < proc->nthreads; k++) { + struct ovni_ethread *thread = &proc->thread[k]; + + emu->global_thread[gi++] = thread; + } + } + } +} + +static void +init_cpus(struct ovni_emu *emu) +{ + struct ovni_trace *trace = &emu->trace; + + emu->global_cpu = calloc(emu->total_ncpus, + sizeof(*emu->global_cpu)); + + if (emu->global_cpu == NULL) { + perror("calloc"); + exit(EXIT_FAILURE); + } + + for (size_t i = 0; i < trace->nlooms; i++) { + struct ovni_loom *loom = &trace->loom[i]; + for (size_t j = 0; j < loom->ncpus; j++) { + struct ovni_cpu *cpu = &loom->cpu[j]; + emu->global_cpu[cpu->gindex] = cpu; + + if (snprintf(cpu->name, MAX_CPU_NAME, "CPU %ld.%ld", i, j) + >= MAX_CPU_NAME) { + err("error cpu %ld.%ld name too long\n", i, j); + exit(EXIT_FAILURE); + } + cpu->virtual = 0; + } + + emu->global_cpu[loom->vcpu.gindex] = &loom->vcpu; + if (snprintf(loom->vcpu.name, MAX_CPU_NAME, "CPU %ld.*", i) + >= MAX_CPU_NAME) { + err("error cpu %ld.* name too long\n", i); + exit(EXIT_FAILURE); + } + loom->vcpu.virtual = 1; + } +} + +static void +create_pcf_cpus(struct ovni_emu *emu) +{ + /* Only needed for the thread PCF */ + struct pcf_file *pcf = &emu->pcf[CHAN_TH]; + int prvtype = chan_to_prvtype[CHAN_OVNI_CPU]; + struct pcf_type *type = pcf_find_type(pcf, prvtype); + + if (type == NULL) + die("cannot find PCF type for CHAN_OVNI_CPU\n"); + + for (size_t i = 0; i < emu->total_ncpus; i++) { + int value = i + 1; + char *label = emu->global_cpu[i]->name; + + pcf_add_value(type, value, label); + } +} + +void +emu_init(struct ovni_emu *emu, int argc, char *argv[]) +{ + memset(emu, 0, sizeof(*emu)); + + parse_args(emu, argc, argv); + + if (ovni_load_trace(&emu->trace, emu->tracedir)) { + err("error loading ovni trace\n"); + exit(EXIT_FAILURE); + } + + if (ovni_load_streams(&emu->trace)) { + err("error loading streams\n"); + exit(EXIT_FAILURE); + } + + load_metadata(emu); + + load_clock_offsets(emu); + + init_threads(emu); + init_cpus(emu); + + open_prvs(emu, emu->tracedir); + open_pcfs(emu, emu->tracedir); + + create_pcf_cpus(emu); + + emu->global_size = 0; + emu->global_offset = 0; + + for (size_t i = 0; i < emu->trace.nstreams; i++) + emu->global_offset += emu->trace.stream[i].offset; + + err("loaded %ld cpus and %ld threads\n", + emu->total_ncpus, + emu->total_nthreads); +} + +static int +copy_file(const char *src, const char *dst) +{ + char buffer[1024]; + + FILE *infile = fopen(src, "r"); + + if (infile == NULL) { + err("fopen(%s) failed: %s\n", src, strerror(errno)); + return -1; + } + + FILE *outfile = fopen(dst, "w"); + + if (outfile == NULL) { + err("fopen(%s) failed: %s\n", src, strerror(errno)); + return -1; + } + + size_t bytes; + while ((bytes = fread(buffer, 1, sizeof(buffer), infile)) > 0) + fwrite(buffer, 1, bytes, outfile); + + fclose(outfile); + fclose(infile); + + return 0; +} + +static int +copy_recursive(const char *src, const char *dst) +{ + DIR *dir; + int failed = 0; + + if ((dir = opendir(src)) == NULL) { + err("opendir \"%s\" failed: %s\n", src, strerror(errno)); + failed = 1; + goto bay; + } + + if (mkdir(dst, 0755) != 0) { + err("mkdir \"%s\" failed: %s\n", src, strerror(errno)); + failed = 1; + goto bay; + } + + /* Use the heap, as the recursion may exhaust the stack */ + char *newsrc = calloc(1, PATH_MAX); + if (newsrc == NULL) + die("calloc failed\n"); + + char *newdst = calloc(1, PATH_MAX); + if (newdst == NULL) + die("calloc failed\n"); + + struct dirent *dirent; + while ((dirent = readdir(dir)) != NULL) { + struct stat st; + sprintf(newsrc, "%s/%s", src, dirent->d_name); + + if (strcmp(dirent->d_name, ".") == 0) + continue; + + if (strcmp(dirent->d_name, "..") == 0) + continue; + + int n = snprintf(newsrc, PATH_MAX, "%s/%s", + src, dirent->d_name); + + if (n >= PATH_MAX) { + err("path too long \"%s/%s\"\n", src, dirent->d_name); + failed = 1; + continue; + } + + int m = snprintf(newdst, PATH_MAX, "%s/%s", + dst, dirent->d_name); + + if (m >= PATH_MAX) { + err("path too long \"%s/%s\"\n", dst, dirent->d_name); + failed = 1; + continue; + } + + if (stat(newsrc, &st) != 0) { + err("stat \"%s\" failed: %s\n", newsrc, + strerror(errno)); + failed = 1; + continue; + } + + if (S_ISDIR(st.st_mode)) { + if (copy_recursive(newsrc, newdst) != 0) { + failed = 1; + } + } else { + if (copy_file(newsrc, newdst) != 0) { + failed = 1; + } + } + } + + closedir(dir); + + free(newsrc); + free(newdst); + +bay: + return -failed; +} + +static void +copy_configs(struct ovni_emu *emu) +{ + /* Allow override so we can run the tests without install */ + char *src = getenv("OVNI_CONFIG_DIR"); + + if (src == NULL) + src = OVNI_CONFIG_DIR; + + char dst[PATH_MAX]; + if (snprintf(dst, PATH_MAX, "%s/cfg", emu->tracedir) >= PATH_MAX) { + err("cannot copy config files: path too long \"%s/cfg\"\n", + emu->tracedir); + return; + } + + struct stat st; + if (stat(dst, &st) == 0) { + err("existing cfg directory \"%s\", skipping config copy\n", dst); + if (emu->enable_linter) + die("cannot continue in linter mode\n"); + return; + } + + if (copy_recursive(src, dst) != 0) { + err("warning: cannot copy config files: recursive copy failed\n"); + if (emu->enable_linter) + die("cannot continue in linter mode\n"); + } +} + +void +emu_post(struct ovni_emu *emu) +{ + /* Write the PCF files */ + pcf_write(&emu->pcf[CHAN_TH]); + pcf_write(&emu->pcf[CHAN_CPU]); + + write_row_cpu(emu); + write_row_thread(emu); + + copy_configs(emu); +} + +void +emu_destroy(struct ovni_emu *emu) +{ + fix_prv_headers(emu); + close_prvs(emu); + close_pcfs(emu); + destroy_metadata(emu); + ovni_free_streams(&emu->trace); + ovni_free_trace(&emu->trace); + + free(emu->global_cpu); + free(emu->global_thread); +} + +void +edie(struct ovni_emu *emu, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + fprintf(stderr, "fatal: "); + vfprintf(stderr, fmt, args); + va_end(args); + + fprintf(stderr, "fatal: while evaluating the event %c%c%c with clock=%ld in thread=%d\n", + emu->cur_ev->header.model, + emu->cur_ev->header.category, + emu->cur_ev->header.value, + emu->cur_ev->header.clock, + emu->cur_thread->tid); + + abort(); +} + +void +eerr(struct ovni_emu *emu, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + fprintf(stderr, "fatal: "); + vfprintf(stderr, fmt, args); + va_end(args); + + fprintf(stderr, "fatal: while evaluating the event %c%c%c with clock=%ld in thread=%d\n", + emu->cur_ev->header.model, + emu->cur_ev->header.category, + emu->cur_ev->header.value, + emu->cur_ev->header.clock, + emu->cur_thread->tid); +} diff --git a/src/emu/old_emu.h b/src/emu/old_emu.h new file mode 100644 index 0000000..ce8338e --- /dev/null +++ b/src/emu/old_emu.h @@ -0,0 +1,586 @@ +/* Copyright (c) 2021 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef OVNI_EMU_H +#define OVNI_EMU_H + +#include + +#include "common.h" +#include "heap.h" +#include "ovni.h" +#include "parson.h" +#include "pcf.h" +#include "uthash.h" + +/* Emulated thread runtime status */ +enum ethread_state { + TH_ST_UNKNOWN, + TH_ST_RUNNING, + TH_ST_PAUSED, + TH_ST_DEAD, + TH_ST_COOLING, + TH_ST_WARMING, +}; + +enum task_state { + TASK_ST_CREATED, + TASK_ST_RUNNING, + TASK_ST_PAUSED, + TASK_ST_DEAD, +}; + +enum ovni_state { + ST_OVNI_FLUSHING = 1, +}; + +enum error_values { + ST_BAD = 666, + ST_TOO_MANY_TH = 777, +}; + +enum nosv_ss_values { + ST_NULL = 0, + ST_NOSV_SCHED_HUNGRY = 6, + ST_NOSV_SCHED_SERVING, + ST_NOSV_SCHED_SUBMITTING, + ST_NOSV_MEM_ALLOCATING, + ST_NOSV_MEM_FREEING, + ST_NOSV_TASK_RUNNING, + ST_NOSV_API_SUBMIT, + ST_NOSV_API_PAUSE, + ST_NOSV_API_YIELD, + ST_NOSV_API_WAITFOR, + ST_NOSV_API_SCHEDPOINT, + ST_NOSV_ATTACH, + ST_NOSV_WORKER, + ST_NOSV_DELEGATE, + + EV_NOSV_SCHED_RECV, + EV_NOSV_SCHED_SEND, + EV_NOSV_SCHED_SELF, +}; + +enum tampi_state { + ST_TAMPI_SEND = 1, + ST_TAMPI_RECV = 2, + ST_TAMPI_ISEND = 3, + ST_TAMPI_IRECV = 4, + ST_TAMPI_WAIT = 5, + ST_TAMPI_WAITALL = 6, +}; + +enum openmp_state { + ST_OPENMP_TASK = 1, + ST_OPENMP_PARALLEL = 2, +}; + +enum nodes_state { + ST_NODES_REGISTER = 1, + ST_NODES_UNREGISTER = 2, + ST_NODES_IF0_WAIT = 3, + ST_NODES_IF0_INLINE = 4, + ST_NODES_TASKWAIT = 5, + ST_NODES_CREATE = 6, + ST_NODES_SUBMIT = 7, + ST_NODES_SPAWN = 8, +}; + +/* The values of nanos6_ss_state are synced to the previous + * CTF implementation. */ +enum nanos6_ss_state { + ST_NANOS6_TASK_BODY = 1, + ST_NANOS6_TASK_CREATING, + ST_NANOS6_TASK_SUBMIT, + ST_NANOS6_TASK_SPAWNING, + ST_NANOS6_TASK_FOR, + ST_NANOS6_SCHED_ADDING, + ST_NANOS6_SCHED_PROCESSING, + ST_NANOS6_SCHED_SERVING, + ST_NANOS6_DEP_REG, + ST_NANOS6_DEP_UNREG, + ST_NANOS6_BLK_TASKWAIT, + ST_NANOS6_BLK_WAITFOR, + ST_NANOS6_BLK_BLOCKING, + ST_NANOS6_BLK_UNBLOCKING, + ST_NANOS6_ALLOCATING, + ST_NANOS6_FREEING, + ST_NANOS6_HANDLING_TASK, + ST_NANOS6_WORKER_LOOP, + ST_NANOS6_SWITCH_TO, + ST_NANOS6_MIGRATE, + ST_NANOS6_SUSPEND, + ST_NANOS6_RESUME, + + /* Value 51 is broken in old Paraver */ + EV_NANOS6_SCHED_RECV = 60, + EV_NANOS6_SCHED_SEND, + EV_NANOS6_SCHED_SELF, + EV_NANOS6_CPU_IDLE, + EV_NANOS6_CPU_ACTIVE, + EV_NANOS6_SIGNAL, +}; + +enum nanos6_thread_type { + ST_NANOS6_TH_LEADER = 1, + ST_NANOS6_TH_MAIN = 2, + ST_NANOS6_TH_WORKER = 3, + ST_NANOS6_TH_EXTERNAL = 4, +}; + +enum kernel_cs_state { + ST_KERNEL_CSOUT = 3, +}; + +struct ovni_ethread; +struct ovni_eproc; + +struct task_type { + uint32_t id; /* Per-process task identifier */ + uint32_t gid; /* Global identifier computed from the label */ + char label[MAX_PCF_LABEL]; + UT_hash_handle hh; +}; + +struct task { + uint32_t id; + struct task_type *type; + + /* The thread that has began to execute the task. It cannot + * change after being set, even if the task ends. */ + struct ovni_ethread *thread; + enum task_state state; + UT_hash_handle hh; + + /* List handle for nested task support */ + struct task *next; + struct task *prev; +}; + +struct task_info { + /* Both hash maps of all known tasks and types */ + struct task_type *types; + struct task *tasks; +}; + +struct task_stack { + union { + struct task *top; /* Synctactic sugar */ + struct task *tasks; + }; + struct ovni_ethread *thread; +}; + +#define MAX_CHAN_STACK 512 + +enum chan_track { + /* The channel is manually controlled. */ + CHAN_TRACK_NONE = 0, + + /* Enables the channel when the thread is running only. */ + CHAN_TRACK_TH_RUNNING, + + /* The thread active tracking mode a enables the channel when + * the thread is running, cooling or warming. Otherwise the + * channel is disabled. */ + CHAN_TRACK_TH_ACTIVE, +}; + +enum chan { + CHAN_OVNI_PID, + CHAN_OVNI_TID, + CHAN_OVNI_NRTHREADS, + CHAN_OVNI_STATE, + CHAN_OVNI_APPID, + CHAN_OVNI_CPU, + CHAN_OVNI_FLUSH, + + CHAN_NOSV_TASKID, + CHAN_NOSV_TYPE, + CHAN_NOSV_APPID, + CHAN_NOSV_SUBSYSTEM, + CHAN_NOSV_RANK, + + CHAN_TAMPI_MODE, + CHAN_OPENMP_MODE, + CHAN_NODES_SUBSYSTEM, + + CHAN_NANOS6_TASKID, + CHAN_NANOS6_TYPE, + CHAN_NANOS6_SUBSYSTEM, + CHAN_NANOS6_RANK, + CHAN_NANOS6_THREAD, + + CHAN_KERNEL_CS, + + CHAN_MAX +}; + +enum chan_type { + CHAN_TH = 0, + CHAN_CPU = 1, + CHAN_MAXTYPE = 2, +}; + +enum chan_dirty { + CHAN_CLEAN = 0, + + /* The channel is dirty because it has been enabled or disabled */ + CHAN_DIRTY_ACTIVE = 1, + + /* The channel is dirty because it changed the state */ + CHAN_DIRTY_VALUE = 2, +}; + +/* clang-format off */ +static const int chan_to_prvtype[CHAN_MAX] = { + [CHAN_OVNI_PID] = 1, + [CHAN_OVNI_TID] = 2, + [CHAN_OVNI_NRTHREADS] = 3, + [CHAN_OVNI_STATE] = 4, + [CHAN_OVNI_APPID] = 5, /* Not used */ + [CHAN_OVNI_CPU] = 6, + [CHAN_OVNI_FLUSH] = 7, + [CHAN_NOSV_TASKID] = 10, + [CHAN_NOSV_TYPE] = 11, + [CHAN_NOSV_APPID] = 12, + [CHAN_NOSV_SUBSYSTEM] = 13, + [CHAN_NOSV_RANK] = 14, + [CHAN_TAMPI_MODE] = 20, + [CHAN_OPENMP_MODE] = 25, + [CHAN_NODES_SUBSYSTEM] = 30, + [CHAN_NANOS6_TASKID] = 35, + [CHAN_NANOS6_TYPE] = 36, + [CHAN_NANOS6_SUBSYSTEM] = 37, + [CHAN_NANOS6_RANK] = 38, + [CHAN_NANOS6_THREAD] = 39, + [CHAN_KERNEL_CS] = 45, +}; +/* clang-format on */ + +struct ovni_chan { + /* Channel id */ + enum chan id; + + /* Number of states in the stack */ + int n; + + /* Stack of states */ + int stack[MAX_CHAN_STACK]; + + /* 1 if enabled, 0 if not. */ + int enabled; + + /* What state should be shown in errors */ + int badst; + + /* Last state emitted (-1 otherwise) */ + int lastst; + + /* Punctual event: -1 if not used */ + int ev; + + /* Emit events of this type */ + int type; + + /* A pointer to a clock to sample the time */ + int64_t *clock; + + /* The time of the last state or event */ + int64_t t; + + /* Paraver row */ + int row; + + /* Type of dirty */ + enum chan_dirty dirty; + + /* Where should the events be written to? */ + FILE *prv; + + /* What should cause the channel to become disabled? */ + enum chan_track track; + + /* The thread associated with the channel if any */ + struct ovni_ethread *thread; + + /* The CPU associated with the channel if any */ + struct ovni_cpu *cpu; + + struct ovni_chan **update_list; + + /* Used when the channel is a list */ + struct ovni_chan *prev; + struct ovni_chan *next; +}; + +#define MAX_BURSTS 100 + +/* State of each emulated thread */ +struct ovni_ethread { + /* Emulated thread tid */ + pid_t tid; + + int index; + int gindex; + + /* The process associated with this thread */ + struct ovni_eproc *proc; + + enum ethread_state state; + int is_running; + int is_active; + + /* Thread stream */ + struct ovni_stream *stream; + + /* Current cpu */ + struct ovni_cpu *cpu; + + /* FIXME: Use a table with registrable pointers to custom data + * structures */ + + /* Task stacks, top ones are the tasks currently runnable. */ + struct task_stack nosv_task_stack; + struct task_stack nanos6_task_stack; + + /* Channels are used to output the emulator state in PRV */ + struct ovni_chan chan[CHAN_MAX]; + + /* Burst times */ + int nbursts; + int64_t burst_time[MAX_BURSTS]; + + /* These pointers keep a linked list of threads in each CPU */ + struct ovni_ethread *prev; + struct ovni_ethread *next; + + /* Trace file path */ + char tracefile[PATH_MAX]; +}; + +/* State of each emulated process */ +struct ovni_eproc { + int pid; + int index; + int gindex; + int appid; + int rank; + + /* The loom of the current process */ + struct ovni_loom *loom; + + /* Path of the process tracedir */ + char dir[PATH_MAX]; + + /* Threads */ + size_t nthreads; + struct ovni_ethread *thread; + + JSON_Value *meta; + + /* ------ Subsystem specific data --------*/ + /* TODO: Use dynamic allocation */ + + struct task_info nosv_task_info; + struct task_info nanos6_task_info; +}; + + +/* ------------------ emulation ---------------- */ + +enum ovni_cpu_type { + CPU_REAL, + CPU_VIRTUAL, +}; + +enum ovni_cpu_state { + CPU_ST_UNKNOWN, + CPU_ST_READY, +}; + +#define MAX_CPU_NAME 32 + +struct ovni_cpu { + /* Logical index: 0 to ncpus - 1 */ + int i; + + /* Physical id: as reported by lscpu(1) */ + int phyid; + + /* Global index for all CPUs */ + int gindex; + + enum ovni_cpu_state state; + + /* The loom of the CPU */ + struct ovni_loom *loom; + + /* CPU channels */ + struct ovni_chan chan[CHAN_MAX]; + + /* The threads assigned to this CPU */ + size_t nthreads; + struct ovni_ethread *thread; + + /* Running threads */ + size_t nrunning_threads; + struct ovni_ethread *th_running; + + /* Active threads (not paused or dead) */ + size_t nactive_threads; + struct ovni_ethread *th_active; + + /* Cpu name as shown in paraver row */ + char name[MAX_CPU_NAME]; + + /* Is this a virtual CPU? */ + int virtual; +}; + +/* ----------------------- trace ------------------------ */ + +/* State of each loom on post-process */ +struct ovni_loom { + size_t nprocs; + char hostname[OVNI_MAX_HOSTNAME]; + char dname[PATH_MAX]; /* Loom directory name */ + char path[PATH_MAX]; /* Relative to cwd */ + + size_t max_ncpus; + size_t max_phyid; + size_t ncpus; + size_t offset_ncpus; + struct ovni_cpu *cpu; + int rank_enabled; + + int64_t clock_offset; + + /* Virtual CPU */ + struct ovni_cpu vcpu; + + struct ovni_eproc *proc; +}; + +#define MAX_VIRTUAL_EVENTS 16 + +struct ovni_trace { + size_t nlooms; + struct ovni_loom *loom; + + size_t nstreams; + struct ovni_stream *stream; +}; + +struct ovni_stream { + uint8_t *buf; + size_t size; + size_t offset; + + int tid; + struct ovni_loom *loom; + struct ovni_eproc *proc; + struct ovni_ethread *thread; + int loaded; + int active; + + double progress; + + struct ovni_ev *cur_ev; + int64_t lastclock; + int64_t clock_offset; + + heap_node_t hh; +}; + +struct ovni_emu { + struct ovni_trace trace; + + struct ovni_stream *cur_stream; + struct ovni_ev *cur_ev; + + struct ovni_loom *cur_loom; + struct ovni_eproc *cur_proc; + struct ovni_ethread *cur_thread; + + /* Indexed by gindex */ + struct ovni_ethread **global_thread; + struct ovni_cpu **global_cpu; + + /* Global processed size and offset of all streams */ + size_t global_size; + size_t global_offset; + double start_emulation_time; + + int64_t firstclock; + int64_t lastclock; + int64_t delta_time; + + /* Counters for statistics */ + int64_t nev_processed; + + /* Be strict */ + int enable_linter; + + FILE *prv_thread; + FILE *prv_cpu; + + struct pcf_file pcf[CHAN_MAXTYPE]; + + char *clock_offset_file; + char *tracedir; + + /* Total counters */ + size_t total_nthreads; + size_t total_nprocs; + size_t total_ncpus; + + uint32_t nosv_type_counter; + + /* Keep a list of dirty channels for the CPUs and threads */ + struct ovni_chan *cpu_chan; + struct ovni_chan *th_chan; + + heap_head_t sorted_stream; +}; + +/* Emulator function declaration */ + +void edie(struct ovni_emu *emu, const char *fmt, ...); +void eerr(struct ovni_emu *emu, const char *fmt, ...); + +void hook_init_ovni(struct ovni_emu *emu); +void hook_pre_ovni(struct ovni_emu *emu); + +void hook_init_nosv(struct ovni_emu *emu); +void hook_pre_nosv(struct ovni_emu *emu); +void hook_end_nosv(struct ovni_emu *emu); + +void hook_init_tampi(struct ovni_emu *emu); +void hook_pre_tampi(struct ovni_emu *emu); + +void hook_init_openmp(struct ovni_emu *emu); +void hook_pre_openmp(struct ovni_emu *emu); + +void hook_init_nodes(struct ovni_emu *emu); +void hook_pre_nodes(struct ovni_emu *emu); + +void hook_init_kernel(struct ovni_emu *emu); +void hook_pre_kernel(struct ovni_emu *emu); + +void hook_init_nanos6(struct ovni_emu *emu); +void hook_pre_nanos6(struct ovni_emu *emu); +void hook_end_nanos6(struct ovni_emu *emu); + +struct ovni_cpu *emu_get_cpu(struct ovni_loom *loom, int cpuid); + +struct ovni_ethread *emu_get_thread(struct ovni_eproc *proc, int tid); + +void emu_cpu_update_chan(struct ovni_cpu *cpu, struct ovni_chan *cpu_chan); + +void emu_init(struct ovni_emu *emu, int argc, char *argv[]); +void emu_run(struct ovni_emu *emu); +void emu_post(struct ovni_emu *emu); +void emu_destroy(struct ovni_emu *emu); + +#endif /* OVNI_EMU_H */ diff --git a/src/emu/old_trace.c b/src/emu/old_trace.c new file mode 100644 index 0000000..87e5706 --- /dev/null +++ b/src/emu/old_trace.c @@ -0,0 +1,686 @@ +/* Copyright (c) 2021-2022 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "trace.h" + +#define _GNU_SOURCE +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int +find_dir_prefix_str(const char *dirname, const char *prefix, const char **str) +{ + const char *p = dirname; + + /* Check the prefix */ + if (strncmp(p, prefix, strlen(prefix)) != 0) + return -1; + + p += strlen(prefix); + + /* Find the dot */ + if (*p != '.') + return -1; + + p++; + + if (str) + *str = p; + + return 0; +} + +static int +find_dir_prefix_int(const char *dirname, const char *prefix, int *num) +{ + const char *p = NULL; + + if (find_dir_prefix_str(dirname, prefix, &p) != 0) + return -1; + + /* Convert the suffix string to a number */ + *num = atoi(p); + + return 0; +} + +static size_t +count_dir_prefix(DIR *dir, const char *prefix) +{ + struct dirent *dirent; + size_t n = 0; + + while ((dirent = readdir(dir)) != NULL) { + if (find_dir_prefix_str(dirent->d_name, prefix, NULL) != 0) + continue; + + n++; + } + + return n; +} + +static int +load_thread(struct ovni_ethread *thread, struct ovni_eproc *proc, int index, int tid, char *filepath) +{ + static int total_threads = 0; + + thread->tid = tid; + thread->index = index; + thread->gindex = total_threads++; + thread->state = TH_ST_UNKNOWN; + thread->proc = proc; + + if (strlen(filepath) >= PATH_MAX) { + err("filepath too large: %s\n", filepath); + return -1; + } + + strcpy(thread->tracefile, filepath); + + return 0; +} + +static void +load_proc_metadata(struct ovni_eproc *proc, int *rank_enabled) +{ + JSON_Object *meta = json_value_get_object(proc->meta); + if (meta == NULL) + die("load_proc_metadata: json_value_get_object() failed\n"); + + JSON_Value *appid_val = json_object_get_value(meta, "app_id"); + if (appid_val == NULL) + die("process %d is missing app_id in metadata\n", proc->pid); + + proc->appid = (int) json_number(appid_val); + + JSON_Value *rank_val = json_object_get_value(meta, "rank"); + + if (rank_val != NULL) { + proc->rank = (int) json_number(rank_val); + *rank_enabled = 1; + } else { + proc->rank = -1; + } +} + +static void +check_metadata_version(struct ovni_eproc *proc) +{ + JSON_Object *meta = json_value_get_object(proc->meta); + if (meta == NULL) + die("check_metadata_version: json_value_get_object() failed\n"); + + JSON_Value *version_val = json_object_get_value(meta, "version"); + if (version_val == NULL) { + die("process %d is missing attribute \"version\" in metadata\n", + proc->pid); + } + + int version = (int) json_number(version_val); + + if (version != OVNI_METADATA_VERSION) { + die("pid %d: metadata version mismatch %d (expected %d)\n", + proc->pid, version, + OVNI_METADATA_VERSION); + } + + JSON_Value *mversion_val = json_object_get_value(meta, "model_version"); + if (mversion_val == NULL) { + die("process %d is missing attribute \"model_version\" in metadata\n", + proc->pid); + } + + const char *mversion = json_string(mversion_val); + + if (strcmp(mversion, OVNI_MODEL_VERSION) != 0) { + die("pid %d: metadata model version mismatch '%s' (expected '%s')\n", + proc->pid, mversion, + OVNI_MODEL_VERSION); + } +} + +static int +compare_int(const void *a, const void *b) +{ + int aa = *(const int *) a; + int bb = *(const int *) b; + + if (aa < bb) + return -1; + else if (aa > bb) + return +1; + else + return 0; +} + +static void +check_metadata_version(struct ovni_eproc *proc) +{ + JSON_Object *meta = json_value_get_object(proc->meta); + if (meta == NULL) + die("check_metadata_version: json_value_get_object() failed\n"); + + JSON_Value *version_val = json_object_get_value(meta, "version"); + if (version_val == NULL) { + die("process %d is missing attribute \"version\" in metadata\n", + proc->pid); + } + + int version = (int) json_number(version_val); + + if (version != OVNI_METADATA_VERSION) { + die("pid %d: metadata version mismatch %d (expected %d)\n", + proc->pid, version, + OVNI_METADATA_VERSION); + } + + JSON_Value *mversion_val = json_object_get_value(meta, "model_version"); + if (mversion_val == NULL) { + die("process %d is missing attribute \"model_version\" in metadata\n", + proc->pid); + } + + const char *mversion = json_string(mversion_val); + + if (strcmp(mversion, OVNI_MODEL_VERSION) != 0) { + die("pid %d: metadata model version mismatch '%s' (expected '%s')\n", + proc->pid, mversion, + OVNI_MODEL_VERSION); + } +} + +static int +load_proc(struct ovni_eproc *proc, struct ovni_loom *loom, int index, int pid, char *procdir) +{ + static int total_procs = 0; + + proc->pid = pid; + proc->index = index; + proc->gindex = total_procs++; + proc->loom = loom; + + char path[PATH_MAX]; + if (snprintf(path, PATH_MAX, "%s/%s", procdir, "metadata.json") >= PATH_MAX) { + err("snprintf: path too large: %s\n", procdir); + abort(); + } + + proc->meta = json_parse_file_with_comments(path); + if (proc->meta == NULL) { + err("error loading metadata from %s\n", path); + return -1; + } + + check_metadata_version(proc); + + /* The appid is populated from the metadata */ + load_proc_metadata(proc, &loom->rank_enabled); + + DIR *dir; + if ((dir = opendir(procdir)) == NULL) { + fprintf(stderr, "opendir %s failed: %s\n", + procdir, strerror(errno)); + return -1; + } + + proc->nthreads = count_dir_prefix(dir, "thread"); + + if (proc->nthreads <= 0) { + err("cannot find any thread for process %d\n", + proc->pid); + return -1; + } + + proc->thread = calloc(proc->nthreads, sizeof(struct ovni_ethread)); + + if (proc->thread == NULL) { + perror("calloc failed"); + return -1; + } + + int *tids; + + if ((tids = calloc(proc->nthreads, sizeof(int))) == NULL) { + perror("calloc failed\n"); + return -1; + } + + rewinddir(dir); + + for (size_t i = 0; i < proc->nthreads;) { + struct dirent *dirent = readdir(dir); + + if (dirent == NULL) { + err("inconsistent: readdir returned NULL\n"); + return -1; + } + + if (find_dir_prefix_int(dirent->d_name, "thread", &tids[i]) != 0) + continue; + + i++; + } + + closedir(dir); + + /* Sort threads by ascending TID */ + qsort(tids, proc->nthreads, sizeof(int), compare_int); + + for (size_t i = 0; i < proc->nthreads; i++) { + int tid = tids[i]; + + if (snprintf(path, PATH_MAX, "%s/thread.%d", procdir, tid) >= PATH_MAX) { + err("snprintf: path too large: %s\n", procdir); + abort(); + } + + struct ovni_ethread *thread = &proc->thread[i]; + + if (load_thread(thread, proc, i, tid, path) != 0) + return -1; + } + + free(tids); + + return 0; +} + +static int +load_loom(struct ovni_loom *loom, char *loomdir) +{ + DIR *dir = NULL; + + if ((dir = opendir(loomdir)) == NULL) { + fprintf(stderr, "opendir %s failed: %s\n", + loomdir, strerror(errno)); + return -1; + } + + loom->rank_enabled = 0; + loom->nprocs = count_dir_prefix(dir, "proc"); + + if (loom->nprocs <= 0) { + err("cannot find any process directory in loom %s\n", + loom->hostname); + return -1; + } + + loom->proc = calloc(loom->nprocs, sizeof(struct ovni_eproc)); + + if (loom->proc == NULL) { + perror("calloc failed"); + return -1; + } + + rewinddir(dir); + + size_t i = 0; + struct dirent *dirent = NULL; + while ((dirent = readdir(dir)) != NULL) { + int pid; + if (find_dir_prefix_int(dirent->d_name, "proc", &pid) != 0) + continue; + + if (i >= loom->nprocs) { + err("more process than expected\n"); + abort(); + } + + struct ovni_eproc *proc = &loom->proc[i]; + + if (snprintf(proc->dir, PATH_MAX, "%s/%s", loomdir, dirent->d_name) >= PATH_MAX) { + err("error: process dir name %s too long\n", dirent->d_name); + return -1; + } + + if (load_proc(&loom->proc[i], loom, i, pid, proc->dir) != 0) + return -1; + + i++; + } + + if (i != loom->nprocs) { + err("unexpected number of processes\n"); + abort(); + } + + closedir(dir); + + /* Ensure all process have the rank, if enabled in any */ + if (loom->rank_enabled) { + for (i = 0; i < loom->nprocs; i++) { + struct ovni_eproc *proc = &loom->proc[i]; + if (proc->rank < 0) { + die("process %d is missing the rank\n", + proc->pid); + } + } + } + + return 0; +} + +static int +compare_looms(const void *a, const void *b) +{ + struct ovni_loom *la = (struct ovni_loom *) a; + struct ovni_loom *lb = (struct ovni_loom *) b; + return strcmp(la->dname, lb->dname); +} + +static void +loom_to_host(const char *loom_name, char *host, int n) +{ + int i = 0; + for (i = 0; i < n; i++) { + /* Copy until dot or end */ + if (loom_name[i] != '.' && loom_name[i] != '\0') + host[i] = loom_name[i]; + else + break; + } + + if (i == n) + die("loom host name %s too long\n", loom_name); + + host[i] = '\0'; +} + +int +ovni_load_trace(struct ovni_trace *trace, char *tracedir) +{ + DIR *dir = NULL; + + if ((dir = opendir(tracedir)) == NULL) { + err("opendir %s failed: %s\n", tracedir, strerror(errno)); + return -1; + } + + trace->nlooms = count_dir_prefix(dir, "loom"); + + if (trace->nlooms == 0) { + err("cannot find any loom in %s\n", tracedir); + return -1; + } + + trace->loom = calloc(trace->nlooms, sizeof(struct ovni_loom)); + + if (trace->loom == NULL) { + perror("calloc failed\n"); + return -1; + } + + rewinddir(dir); + + size_t l = 0; + struct dirent *dirent = NULL; + + while ((dirent = readdir(dir)) != NULL) { + struct ovni_loom *loom = &trace->loom[l]; + const char *loom_name; + if (find_dir_prefix_str(dirent->d_name, "loom", &loom_name) != 0) { + /* Ignore other files in tracedir */ + continue; + } + + if (l >= trace->nlooms) { + err("extra loom detected\n"); + return -1; + } + + /* Copy the complete loom directory name to looms */ + if (snprintf(loom->dname, PATH_MAX, "%s", dirent->d_name) >= PATH_MAX) { + err("error: loom name %s too long\n", dirent->d_name); + return -1; + } + + l++; + } + + closedir(dir); + + /* Sort the looms, so we get the hostnames in alphanumeric order */ + qsort(trace->loom, trace->nlooms, sizeof(struct ovni_loom), + compare_looms); + + for (size_t i = 0; i < trace->nlooms; i++) { + struct ovni_loom *loom = &trace->loom[i]; + const char *name = NULL; + + if (find_dir_prefix_str(loom->dname, "loom", &name) != 0) { + err("error: mismatch for loom %s\n", loom->dname); + return -1; + } + + loom_to_host(name, loom->hostname, sizeof(loom->hostname)); + + if (snprintf(loom->path, PATH_MAX, "%s/%s", + tracedir, loom->dname) + >= PATH_MAX) { + err("error: loom path %s/%s too long\n", + tracedir, loom->dname); + return -1; + } + + if (load_loom(loom, loom->path) != 0) + return -1; + } + + return 0; +} + +static int +check_stream_header(struct ovni_stream *stream) +{ + int ret = 0; + + if (stream->size < sizeof(struct ovni_stream_header)) { + err("stream %d: incomplete stream header\n", + stream->tid); + return -1; + } + + struct ovni_stream_header *h = + (struct ovni_stream_header *) stream->buf; + + if (memcmp(h->magic, OVNI_STREAM_MAGIC, 4) != 0) { + char magic[5]; + memcpy(magic, h->magic, 4); + magic[4] = '\0'; + err("stream %d: wrong stream magic '%s' (expected '%s')\n", + stream->tid, magic, OVNI_STREAM_MAGIC); + ret = -1; + } + + if (h->version != OVNI_STREAM_VERSION) { + err("stream %d: stream version mismatch %u (expected %u)\n", + stream->tid, h->version, OVNI_STREAM_VERSION); + ret = -1; + } + + return ret; +} + +static int +load_stream_fd(struct ovni_stream *stream, int fd) +{ + struct stat st; + if (fstat(fd, &st) < 0) { + perror("fstat failed"); + return -1; + } + + /* Error because it doesn't have the header */ + if (st.st_size == 0) { + err("stream %d is empty\n", stream->tid); + return -1; + } + + int prot = PROT_READ | PROT_WRITE; + stream->buf = mmap(NULL, st.st_size, prot, MAP_PRIVATE, fd, 0); + + if (stream->buf == MAP_FAILED) { + perror("mmap failed"); + return -1; + } + + stream->size = st.st_size; + + return 0; +} + +static int +load_stream_buf(struct ovni_stream *stream, struct ovni_ethread *thread) +{ + int fd; + + if ((fd = open(thread->tracefile, O_RDWR)) == -1) { + perror("open failed"); + return -1; + } + + if (load_stream_fd(stream, fd) != 0) + return -1; + + if (check_stream_header(stream) != 0) { + err("stream %d: bad header\n", stream->tid); + return -1; + } + + stream->offset = sizeof(struct ovni_stream_header); + + if (stream->offset == stream->size) + stream->active = 0; + else + stream->active = 1; + + /* No need to keep the fd open */ + if (close(fd)) { + perror("close failed"); + return -1; + } + + return 0; +} + +/* Populates the streams in a single array */ +int +ovni_load_streams(struct ovni_trace *trace) +{ + trace->nstreams = 0; + + for (size_t i = 0; i < trace->nlooms; i++) { + struct ovni_loom *loom = &trace->loom[i]; + for (size_t j = 0; j < loom->nprocs; j++) { + struct ovni_eproc *proc = &loom->proc[j]; + for (size_t k = 0; k < proc->nthreads; k++) { + trace->nstreams++; + } + } + } + + trace->stream = calloc(trace->nstreams, sizeof(struct ovni_stream)); + + if (trace->stream == NULL) { + perror("calloc failed"); + return -1; + } + + err("loaded %ld streams\n", trace->nstreams); + + size_t s = 0; + for (size_t i = 0; i < trace->nlooms; i++) { + struct ovni_loom *loom = &trace->loom[i]; + for (size_t j = 0; j < loom->nprocs; j++) { + struct ovni_eproc *proc = &loom->proc[j]; + for (size_t k = 0; k < proc->nthreads; k++) { + struct ovni_ethread *thread = &proc->thread[k]; + struct ovni_stream *stream = &trace->stream[s++]; + + stream->tid = thread->tid; + stream->thread = thread; + stream->proc = proc; + stream->loom = loom; + stream->lastclock = 0; + stream->offset = 0; + stream->cur_ev = NULL; + + if (load_stream_buf(stream, thread) != 0) { + err("load_stream_buf failed\n"); + return -1; + } + } + } + } + + return 0; +} + +void +ovni_free_streams(struct ovni_trace *trace) +{ + for (size_t i = 0; i < trace->nstreams; i++) { + struct ovni_stream *stream = &trace->stream[i]; + if (munmap(stream->buf, stream->size) != 0) + die("munmap stream failed: %s\n", strerror(errno)); + } + + free(trace->stream); +} + +void +ovni_free_trace(struct ovni_trace *trace) +{ + for (size_t i = 0; i < trace->nlooms; i++) { + for (size_t j = 0; j < trace->loom[i].nprocs; j++) { + free(trace->loom[i].proc[j].thread); + } + + free(trace->loom[i].proc); + } + + free(trace->loom); +} + +int +ovni_load_next_event(struct ovni_stream *stream) +{ + if (stream->active == 0) { + dbg("stream is inactive, cannot load more events\n"); + return -1; + } + + /* Only step the offset if we have load an event */ + if (stream->cur_ev != NULL) + stream->offset += ovni_ev_size(stream->cur_ev); + + /* It cannot overflow, otherwise we are reading garbage */ + if (stream->offset > stream->size) + die("ovni_load_next_event: stream offset exceeds size\n"); + + /* We have reached the end */ + if (stream->offset == stream->size) { + stream->active = 0; + stream->cur_ev = NULL; + dbg("stream %d runs out of events\n", stream->tid); + return -1; + } + + stream->cur_ev = (struct ovni_ev *) &stream->buf[stream->offset]; + + return 0; +} diff --git a/src/emu/old_trace.h b/src/emu/old_trace.h new file mode 100644 index 0000000..998a77f --- /dev/null +++ b/src/emu/old_trace.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2021 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef OVNI_TRACE_H +#define OVNI_TRACE_H + +#include "emu.h" +#include "ovni.h" + +int ovni_load_next_event(struct ovni_stream *stream); + +int ovni_load_trace(struct ovni_trace *trace, char *tracedir); + +int ovni_load_streams(struct ovni_trace *trace); + +void ovni_free_streams(struct ovni_trace *trace); + +void ovni_free_trace(struct ovni_trace *trace); + +#endif /* OVNI_TRACE_H */ diff --git a/src/emu/ovni/CMakeLists.txt b/src/emu/ovni/CMakeLists.txt new file mode 100644 index 0000000..be09e14 --- /dev/null +++ b/src/emu/ovni/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) +# SPDX-License-Identifier: GPL-3.0-or-later + +#add_library(ovni_model STATIC +# connect.c +# create.c +# event.c +# probe.c +# trace.c +#) +# +#target_include_directories(ovni_model +# PRIVATE "${CMAKE_SOURCE_DIR}/src/emu") +# +#target_link_libraries(emu ovni_model) diff --git a/src/emu/ovni/connect.c b/src/emu/ovni/connect.c new file mode 100644 index 0000000..2e453eb --- /dev/null +++ b/src/emu/ovni/connect.c @@ -0,0 +1,95 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "ovni_model.h" + +#include "emu.h" +#include "mux.h" + +static int +cb_is_running(struct chan *in, void *ptr) +{ + struct chan *out = ptr; + struct value value; + + if (chan_read(in, &value) != 0) { + err("cb_is_running: chan_read %s failed\n", in->name); + return -1; + } + + if (value.type != VALUE_INT64) + die("wrong value type\n"); + + int st = value.i; + if (st == TH_ST_RUNNING) + value = value_int64(1); + else + value = value_int64(0); + + if (chan_set(out, value) != 0) { + err("cb_is_running: chan_set %s failed\n", out->name); + return -1; + } + + return 0; +} + +static int +cb_is_active(struct chan *in, void *ptr) +{ + struct chan *out = ptr; + struct value value; + + if (chan_read(in, &value) != 0) { + err("cb_is_running: chan_read %s failed\n", in->name); + return -1; + } + + if (value.type != VALUE_INT64) + die("wrong value type\n"); + + int st = value.i; + if (st == TH_ST_RUNNING || st == TH_ST_COOLING || st == TH_ST_WARMING) + value = value_int64(1); + else + value = value_int64(0); + + if (chan_set(out, value) != 0) { + err("cb_is_running: chan_set %s failed\n", out->name); + return -1; + } + + return 0; +} + +static struct chan * +find_thread_chan(struct bay *bay, long th_gindex, char *name) +{ + char fullname[MAX_CHAN_NAME]; + sprintf(fullname, "ovni.thread%ld.%s", th_gindex, name); + return bay_find(bay, fullname); +} + +static void +track_thread_state(struct bay *bay, long th_gindex) +{ + struct chan *state = find_thread_chan(bay, th_gindex, "state"); + struct chan *is_running = find_thread_chan(bay, th_gindex, "is_running"); + struct chan *is_active = find_thread_chan(bay, th_gindex, "is_active"); + + if (bay_add_cb(bay, BAY_CB_DIRTY, state, cb_is_running, is_running) != 0) + die("bay_add_cb failed\n"); + if (bay_add_cb(bay, BAY_CB_DIRTY, state, cb_is_active, is_active) != 0) + die("bay_add_cb failed\n"); +} + +int +ovni_model_connect(void *ptr) +{ + struct emu *emu = emu_get(ptr); + + for (size_t i = 0; i < emu->system.nthreads; i++) + track_thread_state(&emu->bay, i); + + return 0; +} diff --git a/src/emu/ovni/create.c b/src/emu/ovni/create.c new file mode 100644 index 0000000..b0c9acf --- /dev/null +++ b/src/emu/ovni/create.c @@ -0,0 +1,110 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "ovni_model.h" + +#include "emu.h" +#include "bay.h" + +struct model_spec ovni_model_spec = { + .name = "ovni", + .model = 'O', + .create = ovni_model_create, + .connect = ovni_model_connect, + .event = ovni_model_event, + .probe = ovni_model_probe, +}; + +static char *th_channels[] = { + "state", + "is_running", + "is_active", + "tid_active", + "pid_active", + "cpu", + "is_flushing" +}; + +static char *cpu_channels[] = { + "tid_running", + "pid_running", + "nthreads_running", + "is_flushing" +}; + +struct pcf_value_label ovni_state_values[] = { + { TH_ST_UNKNOWN, "Unknown" }, + { TH_ST_RUNNING, "Running" }, + { TH_ST_PAUSED, "Paused" }, + { TH_ST_DEAD, "Dead" }, + { TH_ST_COOLING, "Cooling" }, + { TH_ST_WARMING, "Warming" }, + { -1, NULL }, +}; + +struct pcf_value_label ovni_flush_values[] = { + { 0, "None" }, + { ST_OVNI_FLUSHING, "Flushing" }, + { ST_TOO_MANY_TH, "Unknown flushing state: Multiple threads running" }, + { -1, NULL }, +}; + +// [CHAN_OVNI_PID] = "PID", +// [CHAN_OVNI_TID] = "TID", +// [CHAN_OVNI_NRTHREADS] = "Number of RUNNING threads", +// [CHAN_OVNI_STATE] = "Execution state", +// [CHAN_OVNI_APPID] = "AppID", +// [CHAN_OVNI_CPU] = "CPU affinity", +// [CHAN_OVNI_FLUSH] = "Flushing state", + +static void +create_chan(struct bay *bay, char *group, int64_t gid, char *item) +{ + char name[MAX_CHAN_NAME]; + sprintf(name, "%s.%s%ld.%s", ovni_model_spec.name, group, gid, item); + + struct chan *c = calloc(1, sizeof(struct chan)); + if (c == NULL) + die("calloc failed\n"); + + chan_init(c, CHAN_SINGLE, name); + if (bay_register(bay, c) != 0) + die("bay_register failed\n"); +} + +static void +create_thread(struct bay *bay, int64_t gid) +{ + for (size_t i = 0; i < ARRAYLEN(th_channels); i++) + create_chan(bay, "thread", gid, th_channels[i]); +} + +static void +create_cpu(struct bay *bay, int64_t gid) +{ + for (size_t i = 0; i < ARRAYLEN(cpu_channels); i++) + create_chan(bay, "cpu", gid, cpu_channels[i]); +} + +static void +create_channels(struct emu_system *sys, struct bay *bay) +{ + for (size_t i = 0; i < sys->nthreads; i++) + create_thread(bay, i); + + for (size_t i = 0; i < sys->ncpus; i++) + create_cpu(bay, i); +} + +int +ovni_model_create(void *p) +{ + struct emu *emu = emu_get(p); + + /* Get paraver traces */ + //oemu->pvt_thread = pvman_new(emu->pvman, "thread"); + //oemu->pvt_cpu = pvman_new(emu->pvman, "cpu"); + + create_channels(&emu->system, &emu->bay); + return 0; +} diff --git a/src/emu/ovni/event.c b/src/emu/ovni/event.c new file mode 100644 index 0000000..94e8c2f --- /dev/null +++ b/src/emu/ovni/event.c @@ -0,0 +1,516 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "ovni_model.h" +#include "emu.h" + + + +/* Sets the state of the thread and updates the thread tracking channels */ +static void +thread_set_state(struct emu_thread *th, enum ethread_state state) +{ + /* The state must be updated when a cpu is set */ + if (th->cpu == NULL) + die("thread_set_state: thread %d doesn't have a CPU\n", + th->tid); + + dbg("thread_set_state: setting thread %d state %d\n", + th->tid, state); + + th->state = state; + + th->is_running = (state == TH_ST_RUNNING) ? 1 : 0; + + th->is_active = (state == TH_ST_RUNNING + || state == TH_ST_COOLING + || state == TH_ST_WARMING) + ? 1 + : 0; + + chan_set(&th->chan[CHAN_OVNI_STATE], th->state); + + /* Enable or disable the thread channels that track the thread state */ + for (int i = 0; i < CHAN_MAX; i++) + chan_tracking_update(&th->chan[i], th); + + dbg("thread_set_state: done\n"); +} + +static void +cpu_update_th_stats(struct emu_cpu *cpu) +{ + struct emu_thread *th = NULL; + struct emu_thread *th_running = NULL; + struct emu_thread *th_active = NULL; + int active = 0, running = 0; + + DL_FOREACH(cpu->thread, th) + { + if (th->state == TH_ST_RUNNING) { + th_running = th; + running++; + th_active = th; + active++; + } else if (th->state == TH_ST_COOLING || th->state == TH_ST_WARMING) { + th_active = th; + active++; + } + } + + cpu->nrunning_threads = running; + cpu->nactive_threads = active; + cpu->th_running = th_running; + cpu->th_active = th_active; +} + +static void +update_cpu(struct emu_cpu *cpu) +{ + dbg("updating cpu %s\n", cpu->name); + + /* Update the running and active threads first */ + cpu_update_th_stats(cpu); + + /* From the CPU channels we only need to manually update the number of + * threads running in the CPU */ + if (chan_get_st(&cpu->chan[CHAN_OVNI_NRTHREADS]) != (int) cpu->nrunning_threads) + chan_set(&cpu->chan[CHAN_OVNI_NRTHREADS], (int) cpu->nrunning_threads); + + /* Update all tracking channels */ + for (int i = 0; i < CHAN_MAX; i++) + emu_cpu_update_chan(cpu, &cpu->chan[i]); + + dbg("updating cpu %s complete!\n", cpu->name); +} + +struct emu_cpu * +emu_get_cpu(struct emu_loom *loom, int cpuid) +{ + if (cpuid >= (int) loom->ncpus) + die("emu_get_cpu: CPU index out of bounds\n"); + + if (cpuid < 0) + return &loom->vcpu; + + return &loom->cpu[cpuid]; +} + +static struct emu_thread * +emu_cpu_find_thread(struct emu_cpu *cpu, struct emu_thread *thread) +{ + struct emu_thread *p = NULL; + + DL_FOREACH(cpu->thread, p) + { + if (p == thread) + return p; + } + + return NULL; +} + +/* Add the given thread to the list of threads assigned to the CPU */ +static void +cpu_add_thread(struct emu_cpu *cpu, struct emu_thread *thread) +{ + /* Found, abort */ + if (emu_cpu_find_thread(cpu, thread) != NULL) { + err("The thread %d is already assigned to %s\n", + thread->tid, cpu->name); + abort(); + } + + DL_APPEND(cpu->thread, thread); + cpu->nthreads++; + + update_cpu(cpu); +} + +static void +cpu_remove_thread(struct emu_cpu *cpu, struct emu_thread *thread) +{ + struct emu_thread *p = emu_cpu_find_thread(cpu, thread); + + /* Not found, abort */ + if (p == NULL) { + err("cannot remove missing thread %d from cpu %s\n", + thread->tid, cpu->name); + abort(); + } + + DL_DELETE(cpu->thread, thread); + cpu->nthreads--; + + update_cpu(cpu); +} + +static void +cpu_migrate_thread(struct emu_cpu *cpu, + struct emu_thread *thread, + struct emu_cpu *newcpu) +{ + cpu_remove_thread(cpu, thread); + cpu_add_thread(newcpu, thread); +} + +static int +pre_thread_execute(struct emu *emu, struct emu_thread *th) +{ + /* The thread cannot be already running */ + if (th->state == TH_ST_RUNNING) { + err("pre_thread_execute: thread %d already running\n", th->tid); + return -1; + } + + int cpuid = emu->ev->payload.i32[0]; + struct emu_cpu *cpu = emu_system_get_cpu(emu->loom, cpuid); + dbg("pre_thread_execute: thread %d runs in CPU %s\n", th->tid, cpu->name); + + /* First set the CPU in the thread */ + thread_set_cpu(th, cpu); + + /* Then set the thread to running state */ + thread_set_state(th, TH_ST_RUNNING); + + /* And then add the thread to the CPU, so tracking channels see the + * updated thread state */ + cpu_add_thread(cpu, th); +} + +//static void +//pre_thread_end(struct emu_thread *th) +//{ +// if (th->state != TH_ST_RUNNING && th->state != TH_ST_COOLING) +// die("pre_thread_end: thread %d not running or cooling\n", +// th->tid); +// +// if (th->cpu == NULL) +// die("pre_thread_end: thread %d doesn't have a CPU\n", +// th->tid); +// +// /* First update the thread state */ +// thread_set_state(th, TH_ST_DEAD); +// +// /* Then remove it from the cpu, so channels are properly updated */ +// cpu_remove_thread(th->cpu, th); +// +// thread_unset_cpu(th); +//} +// +//static void +//pre_thread_pause(struct emu_thread *th) +//{ +// if (th->state != TH_ST_RUNNING && th->state != TH_ST_COOLING) +// die("pre_thread_pause: thread %d not running or cooling\n", +// th->tid); +// +// if (th->cpu == NULL) +// die("pre_thread_pause: thread %d doesn't have a CPU\n", +// th->tid); +// +// thread_set_state(th, TH_ST_PAUSED); +// update_cpu(th->cpu); +//} +// +//static void +//pre_thread_resume(struct emu_thread *th) +//{ +// if (th->state != TH_ST_PAUSED && th->state != TH_ST_WARMING) +// die("pre_thread_resume: thread %d not paused or warming\n", +// th->tid); +// +// if (th->cpu == NULL) +// die("pre_thread_resume: thread %d doesn't have a CPU\n", +// th->tid); +// +// thread_set_state(th, TH_ST_RUNNING); +// update_cpu(th->cpu); +//} +// +//static void +//pre_thread_cool(struct emu_thread *th) +//{ +// if (th->state != TH_ST_RUNNING) +// die("pre_thread_cool: thread %d not running\n", +// th->tid); +// +// if (th->cpu == NULL) +// die("pre_thread_cool: thread %d doesn't have a CPU\n", +// th->tid); +// +// thread_set_state(th, TH_ST_COOLING); +// update_cpu(th->cpu); +//} +// +//static void +//pre_thread_warm(struct emu_thread *th) +//{ +// if (th->state != TH_ST_PAUSED) +// die("pre_thread_warm: thread %d not paused\n", +// th->tid); +// +// if (th->cpu == NULL) +// die("pre_thread_warm: thread %d doesn't have a CPU\n", +// th->tid); +// +// thread_set_state(th, TH_ST_WARMING); +// update_cpu(th->cpu); +//} + +static int +pre_thread(struct emu *emu) +{ + struct emu_thread *th = emu->thread; + struct emu_ev *ev = emu->ev; + + switch (ev->v) { + case 'C': /* create */ + dbg("thread %d creates a new thread at cpu=%d with args=%x %x\n", + th->tid, + ev->payload->u32[0], + ev->payload->u32[1], + ev->payload->u32[2]); + + break; + case 'x': + return pre_thread_execute(emu, th); +// case 'e': +// return pre_thread_end(th); +// case 'p': +// return pre_thread_pause(th); +// case 'r': +// return pre_thread_resume(th); +// case 'c': +// return pre_thread_cool(th); +// case 'w': +// return pre_thread_warm(th); + default: + err("unknown thread event value %c\n", ev->v); +// return -1; + } + return 0; +} + +//static void +//pre_affinity_set(struct emu *emu) +//{ +// struct emu_thread *th = emu->cur_thread; +// int cpuid = emu->ev->payload.i32[0]; +// +// if (th->cpu == NULL) +// edie(emu, "pre_affinity_set: thread %d doesn't have a CPU\n", +// th->tid); +// +// if (!th->is_active) +// edie(emu, "pre_affinity_set: thread %d is not active\n", +// th->tid); +// +// /* Migrate current cpu to the one at cpuid */ +// struct emu_cpu *newcpu = emu_get_cpu(emu->cur_loom, cpuid); +// +// /* The CPU is already properly set, return */ +// if (th->cpu == newcpu) +// return; +// +// cpu_migrate_thread(th->cpu, th, newcpu); +// thread_migrate_cpu(th, newcpu); +// +// // dbg("cpu %d now runs %d\n", cpuid, th->tid); +//} +// +//static void +//pre_affinity_remote(struct emu *emu) +//{ +// int32_t cpuid = emu->ev->payload.i32[0]; +// int32_t tid = emu->ev->payload.i32[1]; +// +// struct emu_thread *remote_th = emu_get_thread(emu->cur_proc, tid); +// +// if (remote_th == NULL) { +// /* Search the thread in other processes of the loom if +// * not found in the current one */ +// struct emu_loom *loom = emu->cur_loom; +// +// for (size_t i = 0; i < loom->nprocs; i++) { +// struct ovni_eproc *proc = &loom->proc[i]; +// +// /* Skip the current process */ +// if (proc == emu->cur_proc) +// continue; +// +// remote_th = emu_get_thread(proc, tid); +// +// if (remote_th) +// break; +// } +// +// if (remote_th == NULL) { +// err("thread tid %d not found: cannot set affinity remotely\n", +// tid); +// abort(); +// } +// } +// +// /* The remote_th cannot be in states dead or unknown */ +// if (remote_th->state == TH_ST_DEAD) +// edie(emu, "pre_affinity_remote: remote thread %d in state DEAD\n", +// remote_th->tid); +// +// if (remote_th->state == TH_ST_UNKNOWN) +// edie(emu, "pre_affinity_remote: remote thread %d in state UNKNOWN\n", +// remote_th->tid); +// +// /* It must have an assigned CPU */ +// if (remote_th->cpu == NULL) +// edie(emu, "pre_affinity_remote: remote thread %d has no CPU\n", +// remote_th->tid); +// +// /* Migrate current cpu to the one at cpuid */ +// struct emu_cpu *newcpu = emu_get_cpu(emu->cur_loom, cpuid); +// +// cpu_migrate_thread(remote_th->cpu, remote_th, newcpu); +// thread_migrate_cpu(remote_th, newcpu); +// +// // dbg("remote_th %d switches to cpu %d by remote petition\n", tid, +// // cpuid); +//} +// +//static void +//pre_affinity(struct emu *emu) +//{ +// // emu_emit(emu); +// switch (emu->ev->v) { +// case 's': +// pre_affinity_set(emu); +// break; +// case 'r': +// pre_affinity_remote(emu); +// break; +// default: +// dbg("unknown affinity event value %c\n", +// emu->ev->v); +// break; +// } +//} +// +//static int +//compare_int64(const void *a, const void *b) +//{ +// int64_t aa = *(const int64_t *) a; +// int64_t bb = *(const int64_t *) b; +// +// if (aa < bb) +// return -1; +// else if (aa > bb) +// return +1; +// else +// return 0; +//} +// +//static void +//pre_burst(struct emu *emu) +//{ +// int64_t dt = 0; +// +// UNUSED(dt); +// +// struct emu_thread *th = emu->cur_thread; +// +// if (th->nbursts >= MAX_BURSTS) { +// err("too many bursts: ignored\n"); +// return; +// } +// +// th->burst_time[th->nbursts++] = emu->delta_time; +// if (th->nbursts == MAX_BURSTS) { +// int n = MAX_BURSTS - 1; +// int64_t deltas[MAX_BURSTS - 1]; +// for (int i = 0; i < n; i++) { +// deltas[i] = th->burst_time[i + 1] - th->burst_time[i]; +// } +// +// qsort(deltas, n, sizeof(int64_t), compare_int64); +// +// double avg = 0.0; +// double maxdelta = 0; +// for (int i = 0; i < n; i++) { +// if (deltas[i] > maxdelta) +// maxdelta = deltas[i]; +// avg += deltas[i]; +// } +// +// avg /= (double) n; +// double median = deltas[n / 2]; +// +// err("%s burst stats: median %.0f ns, avg %.1f ns, max %.0f ns\n", +// emu->cur_loom->dname, median, avg, maxdelta); +// +// th->nbursts = 0; +// } +//} +// +//static void +//pre_flush(struct emu *emu) +//{ +// struct emu_thread *th = emu->cur_thread; +// struct ovni_chan *chan_th = &th->chan[CHAN_OVNI_FLUSH]; +// +// switch (emu->ev->v) { +// case '[': +// chan_push(chan_th, ST_OVNI_FLUSHING); +// break; +// case ']': +// chan_pop(chan_th, ST_OVNI_FLUSHING); +// break; +// default: +// err("unexpected value '%c' (expecting '[' or ']')\n", +// emu->ev->v); +// abort(); +// } +//} + +static int +hook_pre_ovni(struct emu *emu) +{ + if (emu->ev->m != 'O') + return -1; + + switch (emu->ev->c) { + case 'H': + return pre_thread(emu); +// case 'A': +// pre_affinity(emu); +// break; +// case 'B': +// pre_burst(emu); +// break; +// case 'F': +// pre_flush(emu); +// break; + default: + err("unknown ovni event category %c\n", + emu->ev->c); + return -1; + } + + return 0; +} + +int +ovni_model_event(void *ptr) +{ + struct emu *emu = emu_get(ptr); + if (emu->ev->m != 'O') { + err("unexpected event model %c\n", emu->ev->m); + return -1; + } + + err("got ovni event '%s'\n", emu->ev->mcv); + if (hook_pre_ovni(emu) != 0) { + err("ovni_model_event: failed to process event\n"); + return -1; + } + + return 0; +} diff --git a/src/emu/ovni/ovni_model.h b/src/emu/ovni/ovni_model.h new file mode 100644 index 0000000..8c65882 --- /dev/null +++ b/src/emu/ovni/ovni_model.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef OVNI_MODEL_H +#define OVNI_MODEL_H + +/* The emulator ovni module provides the execution model by + * tracking the thread state and which threads run in each + * CPU */ + +/* Cannot depend on emu.h */ +#include "emu_model.h" +#include "chan.h" +#include + +enum ovni_flushing_state { + ST_OVNI_FLUSHING = 1, +}; + +enum ovni_chan { + CHAN_OVNI_PID = 0, + CHAN_OVNI_TID, + CHAN_OVNI_NRTHREADS, + CHAN_OVNI_STATE, + CHAN_OVNI_APPID, + CHAN_OVNI_CPU, + CHAN_OVNI_FLUSH, + CHAN_OVNI_MAX, +}; + +#define MAX_BURSTS 100 + +struct ovni_cpu { + /* CPU channels */ + struct chan chan[CHAN_OVNI_MAX]; + struct chan pid_running; + struct chan tid_running; + struct chan nthreads_running; +}; + +struct ovni_thread { + struct chan chan[CHAN_OVNI_MAX]; + + struct chan cpu; + struct chan flush; + + /* Burst times */ + int nbursts; + int64_t burst_time[MAX_BURSTS]; +}; + +extern struct model_spec ovni_model_spec; + +int ovni_model_probe(void *ptr); +int ovni_model_create(void *ptr); +int ovni_model_connect(void *ptr); +int ovni_model_event(void *ptr); + +#endif /* OVNI_MODEL_H */ diff --git a/src/emu/ovni/probe.c b/src/emu/ovni/probe.c new file mode 100644 index 0000000..ea994e6 --- /dev/null +++ b/src/emu/ovni/probe.c @@ -0,0 +1,14 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "ovni_model.h" + +#include "emu.h" + +int +ovni_model_probe(void *ptr) +{ + struct emu *emu = emu_get(ptr); + + return emu->system.nthreads > 0; +} diff --git a/src/emu/ovni/trace.c b/src/emu/ovni/trace.c new file mode 100644 index 0000000..435c628 --- /dev/null +++ b/src/emu/ovni/trace.c @@ -0,0 +1,672 @@ +/* Copyright (c) 2021-2022 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#define _GNU_SOURCE + +#include "trace.h" + +#include "ovni.h" +#include "parson.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int +find_dir_prefix_str(const char *dirname, const char *prefix, const char **str) +{ + const char *p = dirname; + + /* Check the prefix */ + if (strncmp(p, prefix, strlen(prefix)) != 0) + return -1; + + p += strlen(prefix); + + /* Find the dot */ + if (*p != '.') + return -1; + + p++; + + if (str) + *str = p; + + return 0; +} + +static int +find_dir_prefix_int(const char *dirname, const char *prefix, int *num) +{ + const char *p = NULL; + + if (find_dir_prefix_str(dirname, prefix, &p) != 0) + return -1; + + /* Convert the suffix string to a number */ + *num = atoi(p); + + return 0; +} + +static size_t +count_dir_prefix(DIR *dir, const char *prefix) +{ + struct dirent *dirent; + size_t n = 0; + + while ((dirent = readdir(dir)) != NULL) { + if (find_dir_prefix_str(dirent->d_name, prefix, NULL) != 0) + continue; + + n++; + } + + return n; +} + +static int +load_thread(struct ovni_thread *thread, struct ovni_proc *proc, int index, int tid, char *filepath) +{ + static int total_threads = 0; + + thread->tid = tid; + thread->index = index; + thread->gindex = total_threads++; + thread->state = TH_ST_UNKNOWN; + thread->proc = proc; + + if (strlen(filepath) >= PATH_MAX) { + err("filepath too large: %s\n", filepath); + return -1; + } + + strcpy(thread->tracefile, filepath); + + return 0; +} + +static void +load_proc_metadata(struct ovni_proc *proc, int *rank_enabled) +{ + JSON_Object *meta = json_value_get_object(proc->meta); + if (meta == NULL) + die("load_proc_metadata: json_value_get_object() failed\n"); + + JSON_Value *appid_val = json_object_get_value(meta, "app_id"); + if (appid_val == NULL) + die("process %d is missing app_id in metadata\n", proc->pid); + + proc->appid = (int) json_number(appid_val); + + JSON_Value *rank_val = json_object_get_value(meta, "rank"); + + if (rank_val != NULL) { + proc->rank = (int) json_number(rank_val); + *rank_enabled = 1; + } else { + proc->rank = -1; + } +} + +static void +check_metadata_version(struct ovni_proc *proc) +{ + JSON_Object *meta = json_value_get_object(proc->meta); + if (meta == NULL) + die("check_metadata_version: json_value_get_object() failed\n"); + + JSON_Value *version_val = json_object_get_value(meta, "version"); + if (version_val == NULL) { + die("process %d is missing attribute \"version\" in metadata\n", + proc->pid); + } + + int version = (int) json_number(version_val); + + if (version != OVNI_METADATA_VERSION) { + die("pid %d: metadata version mismatch %d (expected %d)\n", + proc->pid, version, + OVNI_METADATA_VERSION); + } + + JSON_Value *mversion_val = json_object_get_value(meta, "model_version"); + if (mversion_val == NULL) { + die("process %d is missing attribute \"model_version\" in metadata\n", + proc->pid); + } + + const char *mversion = json_string(mversion_val); + + if (strcmp(mversion, OVNI_MODEL_VERSION) != 0) { + die("pid %d: metadata model version mismatch '%s' (expected '%s')\n", + proc->pid, mversion, + OVNI_MODEL_VERSION); + } +} + +static int +compare_int(const void *a, const void *b) +{ + int aa = *(const int *) a; + int bb = *(const int *) b; + + if (aa < bb) + return -1; + else if (aa > bb) + return +1; + else + return 0; +} + +static int +load_proc(struct ovni_proc *proc, struct ovni_loom *loom, int index, int pid, char *procdir) +{ + static int total_procs = 0; + + proc->pid = pid; + proc->index = index; + proc->gindex = total_procs++; + proc->loom = loom; + + char path[PATH_MAX]; + if (snprintf(path, PATH_MAX, "%s/%s", procdir, "metadata.json") >= PATH_MAX) { + err("snprintf: path too large: %s\n", procdir); + abort(); + } + + proc->meta = json_parse_file_with_comments(path); + if (proc->meta == NULL) { + err("error loading metadata from %s\n", path); + return -1; + } + + check_metadata_version(proc); + + /* The appid is populated from the metadata */ + load_proc_metadata(proc, &loom->rank_enabled); + + DIR *dir; + if ((dir = opendir(procdir)) == NULL) { + fprintf(stderr, "opendir %s failed: %s\n", + procdir, strerror(errno)); + return -1; + } + + proc->nthreads = count_dir_prefix(dir, "thread"); + + if (proc->nthreads <= 0) { + err("cannot find any thread for process %d\n", + proc->pid); + return -1; + } + + proc->thread = calloc(proc->nthreads, sizeof(struct ovni_thread)); + + if (proc->thread == NULL) { + perror("calloc failed"); + return -1; + } + + int *tids; + + if ((tids = calloc(proc->nthreads, sizeof(int))) == NULL) { + perror("calloc failed\n"); + return -1; + } + + rewinddir(dir); + + for (size_t i = 0; i < proc->nthreads;) { + struct dirent *dirent = readdir(dir); + + if (dirent == NULL) { + err("inconsistent: readdir returned NULL\n"); + return -1; + } + + if (find_dir_prefix_int(dirent->d_name, "thread", &tids[i]) != 0) + continue; + + i++; + } + + closedir(dir); + + /* Sort threads by ascending TID */ + qsort(tids, proc->nthreads, sizeof(int), compare_int); + + for (size_t i = 0; i < proc->nthreads; i++) { + int tid = tids[i]; + + if (snprintf(path, PATH_MAX, "%s/thread.%d", procdir, tid) >= PATH_MAX) { + err("snprintf: path too large: %s\n", procdir); + abort(); + } + + struct ovni_thread *thread = &proc->thread[i]; + + if (load_thread(thread, proc, i, tid, path) != 0) + return -1; + } + + free(tids); + + return 0; +} + +static int +load_loom(struct ovni_loom *loom, char *loomdir) +{ + DIR *dir = NULL; + + if ((dir = opendir(loomdir)) == NULL) { + fprintf(stderr, "opendir %s failed: %s\n", + loomdir, strerror(errno)); + return -1; + } + + loom->rank_enabled = 0; + loom->nprocs = count_dir_prefix(dir, "proc"); + + if (loom->nprocs <= 0) { + err("cannot find any process directory in loom %s\n", + loom->hostname); + return -1; + } + + loom->proc = calloc(loom->nprocs, sizeof(struct ovni_proc)); + + if (loom->proc == NULL) { + perror("calloc failed"); + return -1; + } + + rewinddir(dir); + + size_t i = 0; + struct dirent *dirent = NULL; + while ((dirent = readdir(dir)) != NULL) { + int pid; + if (find_dir_prefix_int(dirent->d_name, "proc", &pid) != 0) + continue; + + if (i >= loom->nprocs) { + err("more process than expected\n"); + abort(); + } + + struct ovni_proc *proc = &loom->proc[i]; + + if (snprintf(proc->dir, PATH_MAX, "%s/%s", loomdir, dirent->d_name) >= PATH_MAX) { + err("error: process dir name %s too long\n", dirent->d_name); + return -1; + } + + if (load_proc(&loom->proc[i], loom, i, pid, proc->dir) != 0) + return -1; + + i++; + } + + if (i != loom->nprocs) { + err("unexpected number of processes\n"); + abort(); + } + + closedir(dir); + + /* Ensure all process have the rank, if enabled in any */ + if (loom->rank_enabled) { + for (i = 0; i < loom->nprocs; i++) { + struct ovni_proc *proc = &loom->proc[i]; + if (proc->rank < 0) { + die("process %d is missing the rank\n", + proc->pid); + } + } + } + + return 0; +} + +static int +compare_looms(const void *a, const void *b) +{ + struct ovni_loom *la = (struct ovni_loom *) a; + struct ovni_loom *lb = (struct ovni_loom *) b; + return strcmp(la->dname, lb->dname); +} + +static void +loom_to_host(const char *loom_name, char *host, int n) +{ + int i = 0; + for (i = 0; i < n; i++) { + /* Copy until dot or end */ + if (loom_name[i] != '.' && loom_name[i] != '\0') + host[i] = loom_name[i]; + else + break; + } + + if (i == n) + die("loom host name %s too long\n", loom_name); + + host[i] = '\0'; +} + +int +ovni_load_trace(struct ovni_trace *trace, char *tracedir) +{ + DIR *dir = NULL; + + if ((dir = opendir(tracedir)) == NULL) { + err("opendir %s failed: %s\n", tracedir, strerror(errno)); + return -1; + } + + trace->nlooms = count_dir_prefix(dir, "loom"); + + if (trace->nlooms == 0) { + err("cannot find any loom in %s\n", tracedir); + return -1; + } + + trace->loom = calloc(trace->nlooms, sizeof(struct ovni_loom)); + + if (trace->loom == NULL) { + perror("calloc failed\n"); + return -1; + } + + rewinddir(dir); + + size_t l = 0; + struct dirent *dirent = NULL; + + while ((dirent = readdir(dir)) != NULL) { + struct ovni_loom *loom = &trace->loom[l]; + const char *loom_name; + if (find_dir_prefix_str(dirent->d_name, "loom", &loom_name) != 0) { + /* Ignore other files in tracedir */ + continue; + } + + if (l >= trace->nlooms) { + err("extra loom detected\n"); + return -1; + } + + /* Copy the complete loom directory name to looms */ + if (snprintf(loom->dname, PATH_MAX, "%s", dirent->d_name) >= PATH_MAX) { + err("error: loom name %s too long\n", dirent->d_name); + return -1; + } + + l++; + } + + closedir(dir); + + /* Sort the looms, so we get the hostnames in alphanumeric order */ + qsort(trace->loom, trace->nlooms, sizeof(struct ovni_loom), + compare_looms); + + for (size_t i = 0; i < trace->nlooms; i++) { + struct ovni_loom *loom = &trace->loom[i]; + const char *name = NULL; + + if (find_dir_prefix_str(loom->dname, "loom", &name) != 0) { + err("error: mismatch for loom %s\n", loom->dname); + return -1; + } + + loom_to_host(name, loom->hostname, sizeof(loom->hostname)); + + if (snprintf(loom->path, PATH_MAX, "%s/%s", + tracedir, loom->dname) + >= PATH_MAX) { + err("error: loom path %s/%s too long\n", + tracedir, loom->dname); + return -1; + } + + if (load_loom(loom, loom->path) != 0) + return -1; + } + + return 0; +} + +static int +check_stream_header(struct ovni_stream *stream) +{ + int ret = 0; + + if (stream->size < sizeof(struct ovni_stream_header)) { + err("stream %d: incomplete stream header\n", + stream->tid); + return -1; + } + + struct ovni_stream_header *h = + (struct ovni_stream_header *) stream->buf; + + if (memcmp(h->magic, OVNI_STREAM_MAGIC, 4) != 0) { + char magic[5]; + memcpy(magic, h->magic, 4); + magic[4] = '\0'; + err("stream %d: wrong stream magic '%s' (expected '%s')\n", + stream->tid, magic, OVNI_STREAM_MAGIC); + ret = -1; + } + + if (h->version != OVNI_STREAM_VERSION) { + err("stream %d: stream version mismatch %u (expected %u)\n", + stream->tid, h->version, OVNI_STREAM_VERSION); + ret = -1; + } + + return ret; +} + +static int +load_stream_fd(struct ovni_stream *stream, int fd) +{ + struct stat st; + if (fstat(fd, &st) < 0) { + perror("fstat failed"); + return -1; + } + + /* Error because it doesn't have the header */ + if (st.st_size == 0) { + err("stream %d is empty\n", stream->tid); + return -1; + } + + int prot = PROT_READ | PROT_WRITE; + stream->buf = mmap(NULL, st.st_size, prot, MAP_PRIVATE, fd, 0); + + if (stream->buf == MAP_FAILED) { + perror("mmap failed"); + return -1; + } + + stream->size = st.st_size; + + return 0; +} + +static int +load_stream_buf(struct ovni_stream *stream, struct ovni_thread *thread) +{ + int fd; + + if ((fd = open(thread->tracefile, O_RDWR)) == -1) { + perror("open failed"); + return -1; + } + + if (load_stream_fd(stream, fd) != 0) + return -1; + + if (check_stream_header(stream) != 0) { + err("stream %d: bad header\n", stream->tid); + return -1; + } + + stream->offset = sizeof(struct ovni_stream_header); + + if (stream->offset == stream->size) + stream->active = 0; + else + stream->active = 1; + + /* No need to keep the fd open */ + if (close(fd)) { + perror("close failed"); + return -1; + } + + return 0; +} + +/* Populates the streams in a single array */ +int +ovni_load_streams(struct ovni_trace *trace) +{ + trace->nstreams = 0; + + for (size_t i = 0; i < trace->nlooms; i++) { + struct ovni_loom *loom = &trace->loom[i]; + for (size_t j = 0; j < loom->nprocs; j++) { + struct ovni_proc *proc = &loom->proc[j]; + for (size_t k = 0; k < proc->nthreads; k++) { + trace->nstreams++; + } + } + } + + trace->stream = calloc(trace->nstreams, sizeof(struct ovni_stream)); + + if (trace->stream == NULL) { + perror("calloc failed"); + return -1; + } + + err("loaded %ld streams\n", trace->nstreams); + + size_t s = 0; + for (size_t i = 0; i < trace->nlooms; i++) { + struct ovni_loom *loom = &trace->loom[i]; + for (size_t j = 0; j < loom->nprocs; j++) { + struct ovni_proc *proc = &loom->proc[j]; + for (size_t k = 0; k < proc->nthreads; k++) { + struct ovni_thread *thread = &proc->thread[k]; + struct ovni_stream *stream = &trace->stream[s++]; + + stream->tid = thread->tid; + stream->thread = thread; + stream->proc = proc; + stream->loom = loom; + stream->lastclock = 0; + stream->offset = 0; + stream->cur_ev = NULL; + + if (load_stream_buf(stream, thread) != 0) { + err("load_stream_buf failed\n"); + return -1; + } + } + } + } + + return 0; +} + +void +ovni_free_streams(struct ovni_trace *trace) +{ + for (size_t i = 0; i < trace->nstreams; i++) { + struct ovni_stream *stream = &trace->stream[i]; + if (munmap(stream->buf, stream->size) != 0) + die("munmap stream failed: %s\n", strerror(errno)); + } + + free(trace->stream); +} + +void +ovni_free_trace(struct ovni_trace *trace) +{ + for (size_t i = 0; i < trace->nlooms; i++) { + for (size_t j = 0; j < trace->loom[i].nprocs; j++) { + free(trace->loom[i].proc[j].thread); + } + + free(trace->loom[i].proc); + } + + free(trace->loom); +} + +int +ovni_load_next_event(struct ovni_stream *stream) +{ + if (stream->active == 0) { + dbg("stream is inactive, cannot load more events\n"); + return -1; + } + + /* Only step the offset if we have load an event */ + if (stream->cur_ev != NULL) + stream->offset += ovni_ev_size(stream->cur_ev); + + /* It cannot overflow, otherwise we are reading garbage */ + if (stream->offset > stream->size) + die("ovni_load_next_event: stream offset exceeds size\n"); + + /* We have reached the end */ + if (stream->offset == stream->size) { + stream->active = 0; + stream->cur_ev = NULL; + dbg("stream %d runs out of events\n", stream->tid); + return -1; + } + + stream->cur_ev = (struct ovni_ev *) &stream->buf[stream->offset]; + + return 0; +} + +int +ovni_trace_probe(const char *tracedir) +{ + DIR *dir = NULL; + if ((dir = opendir(tracedir)) == NULL) { + err("trace_probe: opendir %s failed: %s\n", + tracedir, strerror(errno)); + return -1; + } + + int nlooms = count_dir_prefix(dir, "loom"); + closedir(dir); + + /* At least one loom required */ + if (nlooms == 0) + return -1; + else + return 0; +} diff --git a/src/emu/ovni/trace.h b/src/emu/ovni/trace.h new file mode 100644 index 0000000..daaee5c --- /dev/null +++ b/src/emu/ovni/trace.h @@ -0,0 +1,16 @@ +/* Copyright (c) 2021 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef OVNI_TRACE_H +#define OVNI_TRACE_H + +#include "ovni_model.h" + +int ovni_load_next_event(struct ovni_stream *stream); +int ovni_load_trace(struct ovni_trace *trace, char *tracedir); +int ovni_load_streams(struct ovni_trace *trace); +void ovni_free_streams(struct ovni_trace *trace); +void ovni_free_trace(struct ovni_trace *trace); +int ovni_trace_probe(const char *tracedir); + +#endif /* OVNI_TRACE_H */ diff --git a/src/emu/proc.c b/src/emu/proc.c new file mode 100644 index 0000000..321faed --- /dev/null +++ b/src/emu/proc.c @@ -0,0 +1,112 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "emu_system.h" +#include "utlist.h" +#include + +void +proc_init(struct proc *proc, struct loom *loom, pid_t pid) +{ + memset(proc, 0, sizeof(struct proc)); + + if (snprintf(proc->name, PATH_MAX, "%s", name) >= PATH_MAX) + die("new_proc: name too long: %s\n", name); + + if (snprintf(proc->relpath, PATH_MAX, "%s/%s", loom->name, proc->name) >= PATH_MAX) + die("new_proc: relative path too long: %s/%s", loom->name, proc->name); + + if (snprintf(proc->path, PATH_MAX, "%s/%s", tracedir, proc->relpath) >= PATH_MAX) + die("new_proc: path too long: %s/%s", tracedir, proc->relpath); + + proc->pid = pid; + proc->loom = loom; + proc->id = proc->relpath; + + err("new proc '%s'\n", proc->id); +} + +static int +check_proc_metadata(JSON_Value *meta_val, const char *path) +{ + JSON_Object *meta = json_value_get_object(meta_val); + if (meta == NULL) { + err("check_proc_metadata: json_value_get_object() failed: %s\n", + path); + return -1; + } + + JSON_Value *version_val = json_object_get_value(meta, "version"); + if (version_val == NULL) { + err("check_proc_metadata: missing attribute \"version\": %s\n", + path); + return -1; + } + + int version = (int) json_number(version_val); + + if (version != OVNI_METADATA_VERSION) { + err("check_proc_metadata: metadata version mismatch %d (expected %d) in %s\n", + version, OVNI_METADATA_VERSION, path); + return -1; + } + + JSON_Value *mversion_val = json_object_get_value(meta, "model_version"); + if (mversion_val == NULL) { + err("check_proc_metadata: missing attribute \"model_version\" in %s\n", + path); + return -1; + } + + const char *mversion = json_string(mversion_val); + if (strcmp(mversion, OVNI_MODEL_VERSION) != 0) { + err("check_proc_metadata: model version mismatch '%s' (expected '%s') in %s\n", + mversion, OVNI_MODEL_VERSION, path); + return -1; + } + + return 0; +} + +int +proc_load_metadata(struct emu_proc *proc, char *metadata_file) +{ + if (proc->meta != NULL) { + err("proc_load_metadata: process '%s' already has metadata\n", + proc->id); + return -1; + } + + proc->meta = json_parse_file_with_comments(metadata_file); + if (proc->meta == NULL) { + err("proc_load_metadata: failed to load metadata: %s\n", + metadata_file); + return -1; + } + + if (check_proc_metadata(proc->meta, path) != 0) { + err("load_proc_metadata: invalid metadata: %s\n", + metadata_file); + return -1; + } + + /* The appid is populated from the metadata */ + if (load_proc_attributes(proc, path) != 0) { + err("load_proc_metadata: invalid attributes: %s\n", + metadata_file); + return -1; + } + + return 0; +} + +struct thread * +proc_find_thread(struct proc *proc, pid_t tid) +{ + struct thread *th; + DL_FOREACH2(proc->threads, th, lnext) { + if (t->tid == tid) + return t; + } + return NULL; +} diff --git a/src/emu/proc.h b/src/emu/proc.h new file mode 100644 index 0000000..698f6a0 --- /dev/null +++ b/src/emu/proc.h @@ -0,0 +1,47 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef PROC_H +#define PROC_H + +struct proc; + +#include "loom.h" +#include "thread.h" +#include "parson.h" +#include + +struct proc { + size_t gindex; + + char name[PATH_MAX]; /* Proc directory name */ + char fullpath[PATH_MAX]; + char relpath[PATH_MAX]; + char *id; /* Points to relpath */ + + pid_t pid; + int index; + int appid; + int rank; + + struct loom *loom; + + JSON_Value *meta; + + int nthreads; + struct thread *threads; + + /* Local list */ + struct proc *lnext; + struct proc *lprev; + + /* Global list */ + struct proc *gnext; + struct proc *gprev; + + //struct model_ctx ctx; +}; + +void proc_init(struct proc *proc); + +#endif /* PROC_H */ diff --git a/src/emu/pvtrace.c b/src/emu/pvtrace.c new file mode 100644 index 0000000..e69de29 diff --git a/src/emu/pvtrace.h b/src/emu/pvtrace.h new file mode 100644 index 0000000..78d781f --- /dev/null +++ b/src/emu/pvtrace.h @@ -0,0 +1,30 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef PVTRACE_H +#define PVTRACE_H + +#include "prv.h" +#include "pcf.h" +#include "uthash.h" +#include +#include + +struct pvtrace { + char name[PATH_MAX]; + struct prv prv; + struct pcf_file pcf; +}; + +struct pvmanager { + struct pvtrace *traces; +}; + +int pvmanager_init(struct pvmanager *man); +struct pvt *pvman_new(struct pvmanager *man, + const char *path, long nrows); + +struct prv *pvt_get_prv(struct pvtrace *trace); +struct pcf *pvt_get_pcf(struct pvtrace *trace); + +#endif /* PRV_H */ diff --git a/src/emu/thread.c b/src/emu/thread.c new file mode 100644 index 0000000..61c308c --- /dev/null +++ b/src/emu/thread.c @@ -0,0 +1,120 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "thread.h" + +static void +init_chans(struct thread *th) +{ + struct thread_chan *c = &th->chan; + char prefix[128]; + + if (snprintf(prefix, 128, "sys.thread%lu", th->gindex) >= 128) + die("snprintf too long"); + + chan_init(&c->cpu_gindex, CHAN_SINGLE, "%s.cpu_gindex", prefix); + chan_init(&c->tid_active, CHAN_SINGLE, "%s.tid_active", prefix); + chan_init(&c->nth_active, CHAN_SINGLE, "%s.nth_active", prefix); + chan_init(&c->state, CHAN_SINGLE, "%s.state", prefix); +} + +void +thread_init(struct thread *thread, struct proc *proc) +{ + memset(thread, 0, sizeof(struct thread)); + + thread->state = TH_ST_UNKNOWN; + thread->proc = proc; + + init_chans(thread); +} + +/* Sets the state of the thread and updates the thread tracking channels */ +int +thread_set_state(struct thread *th, enum thread_state state) +{ + /* The state must be updated when a cpu is set */ + if (th->cpu == NULL) { + die("thread_set_state: thread %d doesn't have a CPU\n", th->tid); + return -1; + } + + th->state = state; + th->is_running = (state == TH_ST_RUNNING) ? 1 : 0; + th->is_active = (state == TH_ST_RUNNING + || state == TH_ST_COOLING + || state == TH_ST_WARMING) + ? 1 + : 0; + + struct thread_chan *chan = &th->chan; + if (chan_set(&chan->state, value_int64(th->state)) != 0) { + err("thread_set_cpu: chan_set() failed"); + return -1; + } + + return 0; +} + +int +thread_set_cpu(struct thread *th, struct cpu *cpu) +{ + if (cpu == NULL) { + err("thread_set_cpu: CPU is NULL\n"); + return -1; + } + + if (th->cpu != NULL) { + err("thread_set_cpu: thread %d already has a CPU\n", th->tid); + return -1; + } + + th->cpu = cpu; + + /* Update cpu channel */ + struct thread_chan *chan = &th->chan; + if (chan_set(&chan->cpu_gindex, value_int64(cpu->gindex)) != 0) { + err("thread_set_cpu: chan_set failed\n"); + return -1; + } + + return 0; +} + +int +thread_unset_cpu(struct thread *th) +{ + if (th->cpu == NULL) { + err("thread_unset_cpu: thread %d doesn't have a CPU\n", th->tid); + return -1; + } + + th->cpu = NULL; + + struct thread_chan *chan = &th->chan; + if (chan_set(&chan->cpu_gindex, value_null()) != 0) { + err("thread_unset_cpu: chan_set failed\n"); + return -1; + } + + return 0; +} + +int +thread_migrate_cpu(struct thread *th, struct cpu *cpu) +{ + if (th->cpu == NULL) { + die("thread_migrate_cpu: thread %d doesn't have a CPU\n", th->tid); + return -1; + } + + th->cpu = cpu; + + struct thread_chan *chan = &th->chan; + if (chan_set(&chan->cpu_gindex, value_int64(cpu->gindex)) != 0) { + err("thread_migrate_cpu: chan_set failed\n"); + return -1; + } + + return 0; +} diff --git a/src/emu/thread.h b/src/emu/thread.h new file mode 100644 index 0000000..05871ea --- /dev/null +++ b/src/emu/thread.h @@ -0,0 +1,77 @@ +/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef THREAD_H +#define THREAD_H + +struct thread; + +#include "cpu.h" +#include "chan.h" +#include "emu_stream.h" +#include + +/* Emulated thread runtime status */ +enum thread_state { + TH_ST_UNKNOWN, + TH_ST_RUNNING, + TH_ST_PAUSED, + TH_ST_DEAD, + TH_ST_COOLING, + TH_ST_WARMING, +}; + +struct thread_chan { + struct chan cpu_gindex; + struct chan tid_active; + struct chan nth_active; + struct chan state; +}; + +struct thread { + size_t gindex; /* In the system */ + + char name[PATH_MAX]; + char path[PATH_MAX]; + char relpath[PATH_MAX]; + + int tid; + size_t index; /* In loom */ + + /* The process associated with this thread */ + struct proc *proc; + + enum thread_state state; + int is_running; + int is_active; + + /* Stream linked to this thread */ + struct emu_stream *stream; + + /* Current cpu, NULL if not unique affinity */ + struct cpu *cpu; + + /* Linked list of threads in each CPU */ + struct thread *cpu_prev; + struct thread *cpu_next; + + /* Local list */ + struct thread *lprev; + struct thread *lnext; + + /* Global list */ + struct thread *gnext; + struct thread *gprev; + + struct thread_chan chan; + + //struct model_ctx ctx; +}; + +void thread_init(struct thread *thread, struct proc *proc); +int thread_set_state(struct thread *th, enum thread_state state); +int thread_set_cpu(struct thread *th, struct cpu *cpu); +int thread_unset_cpu(struct thread *th); +int thread_migrate_cpu(struct thread *th, struct cpu *cpu); + +#endif /* THREAD_H */