diff --git a/src/emu/CMakeLists.txt b/src/emu/CMakeLists.txt index 0da80e8..403977b 100644 --- a/src/emu/CMakeLists.txt +++ b/src/emu/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) +# Copyright (c) 2021-2024 Barcelona Supercomputing Center (BSC) # SPDX-License-Identifier: GPL-3.0-or-later include_directories( @@ -18,10 +18,12 @@ add_library(emu STATIC emu_args.c emu_ev.c emu_stat.c + ev_spec.c model.c model_cpu.c model_thread.c model_pvt.c + model_evspec.c models.c player.c stream.c diff --git a/src/emu/ev_spec.c b/src/emu/ev_spec.c new file mode 100644 index 0000000..b7d83de --- /dev/null +++ b/src/emu/ev_spec.c @@ -0,0 +1,537 @@ +/* Copyright (c) 2023-2024 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "ev_spec.h" + +#include +#include +#include +#include "common.h" +#include "emu_ev.h" +#include "ovni.h" + +static const char *type_name[MAX_TYPE] = { + [U8] = "u8", + [U16] = "u16", + [U32] = "u32", + [U64] = "u64", + [I8] = "i8", + [I16] = "i16", + [I32] = "i32", + [I64] = "i64", + [STR] = "str", +}; + +static const char *type_fmt[MAX_TYPE] = { + [U8] = "%" PRIu8, + [U16] = "%" PRIu16, + [U32] = "%" PRIu32, + [U64] = "%" PRIu64, + [I8] = "%" PRId8, + [I16] = "%" PRId16, + [I32] = "%" PRId32, + [I64] = "%" PRId64, + [STR] = "%s", +}; + +static size_t type_size[MAX_TYPE] = { + [U8] = 1, + [U16] = 2, + [U32] = 4, + [U64] = 8, + [I8] = 1, + [I16] = 2, + [I32] = 4, + [I64] = 8, + [STR] = 0, /* Has to be computed */ +}; + +struct cursor { + const char *in; /* Pointer to next char in input buffer */ + char *out; /* Pointer to next char in output buffer */ + int len; /* Remaining size in output buffer */ +}; + +static void +advance_out(struct cursor *c, int n) +{ + /* Advance buffer and update len */ + c->out += n; + c->len -= n; +} + +static void +advance_in(struct cursor *c, int n) +{ + c->in += n; +} + +static int +parse_type(struct ev_arg *argspec, char *type) +{ + for (int i = 0; i < MAX_TYPE; i++) { + if (strcmp(type, type_name[i]) == 0) { + argspec->type = (enum ev_arg_type) i; + return 0; + } + } + + err("cannot find matching type for '%s'", type); + return -1; +} + +static int +parse_arg(struct ev_spec *spec, char *arg) +{ + if (spec->nargs >= MAX_ARGS) { + err("too many arguments"); + return -1; + } + + struct ev_arg *argspec = &spec->args[spec->nargs]; + + char *saveptr = NULL; + char *type = strtok_r(arg, " ", &saveptr); + if (type == NULL) { + err("cannot parse type in argument '%s'", arg); + return -1; + } + + char *name = strtok_r(NULL, " ", &saveptr); + if (name == NULL) { + err("cannot parse name in argument '%s'", arg); + return -1; + } + + /* Copy name */ + size_t n = snprintf(argspec->name, sizeof(argspec->name), "%s", name); + if (n >= sizeof(argspec->name)) { + err("argument name too long: %s", name); + return -1; + } + + if (parse_type(argspec, type) != 0) { + err("cannot determine type in argument '%s'", arg); + return -1; + } + + argspec->size = type_size[argspec->type]; + argspec->offset = spec->payload_size; + spec->nargs++; + spec->payload_size += argspec->size; + + return 0; +} + +static int +parse_args(struct ev_spec *spec, char *paren) +{ + paren++; + + /* Skip jumbo size */ + if (spec->is_jumbo) + spec->payload_size = 4; + else + spec->payload_size = 0; + + char *saveptr = NULL; + char *arg = strtok_r(paren, ",)", &saveptr); + while (arg) { + if (parse_arg(spec, arg) != 0) { + err("cannot parse argument '%s'", arg); + return -1; + } + + arg = strtok_r(NULL, ",)", &saveptr); + } + + return 0; +} + +static int +is_mcv_valid(char m, char c, char v) +{ + return isgraph(m) && isgraph(c) && isgraph(v); +} + +static int +parse_signature(struct ev_spec *spec, char *sig) +{ + if (strlen(sig) < 3) { + err("signature too short: %s", sig); + return -1; + } + + char M = sig[0]; + char C = sig[1]; + char V = sig[2]; + + /* The MCV part must be printable */ + if (!is_mcv_valid(M, C, V)) { + err("invalid MCV: %s", sig); + return -1; + } + + spec->mcv[0] = M; + spec->mcv[1] = C; + spec->mcv[2] = V; + spec->mcv[3] = '\0'; + + /* The next character may be '+' if jumbo */ + char *next = &sig[3]; + if (*next == '+') { + spec->is_jumbo = 1; + next++; + } + + /* No arguments */ + if (*next == '\0') { + if (spec->is_jumbo) { + err("missing jumbo arguments in signature: %s", sig); + return -1; + } + + return 0; + } + + /* If there are arguments, it must have one parenthesis */ + if (*next != '(') { + err("expecting parenthesis '(' for arguments: %s", sig); + return -1; + } + + /* Place args pointer to the first parenthesis '(' */ + char *args = next; + + if (parse_args(spec, args) < 0) { + err("cannot parse arguments: %s", sig); + return -1; + } + + /* Must have at least one argument */ + if (spec->nargs == 0) { + err("empty arguments: %s", sig); + return -1; + } + + return 0; +} + +int +ev_spec_compile(struct ev_spec *spec, struct ev_decl *decl) +{ + memset(spec, 0, sizeof(struct ev_spec)); + + /* Working copy so we can modify it */ + char sig[256]; + + if (snprintf(sig, 256, "%s", decl->signature) >= 256) { + err("signature too long: %s", decl->signature); + return -1; + } + + int ret = parse_signature(spec, sig); + + if (ret != 0) { + err("cannot parse signature '%s'", sig); + return -1; + } + + spec->description = decl->description; + + return 0; +} + +struct ev_arg * +ev_spec_find_arg(struct ev_spec *spec, const char *name) +{ + for (int i = 0; i < spec->nargs; i++) { + struct ev_arg *arg = &spec->args[i]; + if (strcmp(arg->name, name) == 0) + return arg; + } + + return NULL; +} + +/* Parse printf format specifier like: + * %3d{cpu} + * | + * c->in points to the next char after the % + * + * Precondition: *c->in != '{'. + * */ +static int +parse_printf_format(char *fmt, int buflen, struct cursor *c) +{ + int n = buflen - 1; + int ifmt = 0; + + /* Check precondition */ + if (*c->in == '{') { + err("missing format"); + return -1; + } + + if (n < 1) { + err("no space for arg name"); + return -1; + } + + if (ifmt >= n) { + err("buffer empty"); + return -1; + } + + /* Always write the first % */ + fmt[ifmt++] = '%'; + + for (; *c->in != '{'; c->in++) { + if (*c->in == '\0') { + err("unexpected end of format"); + return -1; + } + if (ifmt >= n) { + err("format too long"); + return -1; + } + fmt[ifmt++] = *c->in; + } + + /* Complete the printf format */ + fmt[ifmt] = '\0'; + + return 0; +} + +/* Parse argument name specifier like: + * %3d{cpu} + * | + * c->in points to the next char after the { + * + * Precondition: *c->in != '}'. + * */ +static int +parse_arg_name(char *arg, int buflen, struct cursor *c) +{ + int n = buflen - 1; + int iarg = 0; + + /* Check precondition */ + if (*c->in == '}') { + err("missing argument name"); + return -1; + } + + if (n < 1) { + err("no space for arg name"); + return -1; + } + + if (iarg >= n) { + err("buffer empty"); + return -1; + } + + /* Parse argument name */ + for (; *c->in != '}'; c->in++) { + if (*c->in == '\0') { + err("unexpected end of argument name"); + return -1; + } + if (!isalnum(*c->in)) { + err("bad argument name"); + return -1; + } + if (iarg >= n) { + err("argument name too long"); + return -1; + } + arg[iarg++] = *c->in; + } + + arg[iarg] = '\0'; + + return 0; +} + +static int +print_arg(struct ev_arg *arg, const char *fmt, struct cursor *c, struct emu_ev *ev) +{ + int n = 0; + uint8_t *payload = (uint8_t *) ev->payload; + +#define CASE(TYPE) \ + do { \ + TYPE *data = (TYPE *) &payload[arg->offset]; \ + n = snprintf(c->out, c->len, fmt, *data); \ + if (n >= c->len) { \ + err("no space for argument"); \ + return -1; \ + } \ + advance_out(c, n); \ + } while (0); break; + + switch (arg->type) { + case U8: CASE(uint8_t); + case U16: CASE(uint16_t); + case U32: CASE(uint32_t); + case U64: CASE(uint64_t); + case I8: CASE(int8_t); + case I16: CASE(int16_t); + case I32: CASE(int32_t); + case I64: CASE(int64_t); + case STR: + { + char *data = (char *) &payload[arg->offset]; + /* Here we trust the input string to + * contain a nil at the end */ + int n = snprintf(c->out, c->len, fmt, data); + if (n >= c->len) { + err("no space for string argument"); + return -1; + } + advance_out(c, n); + break; + } + default: + err("bad type"); + return -1; + } + +#undef CASE + + return 0; +} + +/* Returns 0 on success or -1 on error. The input and output pointers + * are advanced accordingly. */ +static int +format_region(struct ev_spec *spec, struct cursor *c, struct emu_ev *ev) +{ + if (c->len == 0) { + err("no more room"); + return -1; + } + + /* Begins with percent pointing to %{xxx} */ + if (*c->in != '%') { + err("expecting initial %%"); + return -1; + } + + advance_in(c, 1); /* Skip initial % */ + + /* If the string ends just there is an error, like "xxx %" */ + if (*c->in == '\0') { + err("truncated '%%' format"); + return -1; + } + + /* At least len == 1, no check needed here */ + if (*c->in == '%') { + *c->out = '%'; + advance_out(c, 1); + advance_in(c, 1); /* Eat the second % in the input */ + return 0; + } + + int infer_fmt = 0; + char fmt[64]; + + if (*c->in == '{') { + /* Missing format, use default inferred from the type + * later */ + infer_fmt = 1; + } else { + if (parse_printf_format(fmt, sizeof(fmt), c) != 0) { + err("cannot parse printf format"); + return -1; + } + } + + if (*c->in != '{') { + err("expecting opening bracket"); + return -1; + } + + advance_in(c, 1); /* Skip opening braket */ + + char argname[64]; + + if (parse_arg_name(argname, sizeof(argname), c) != 0) { + err("cannot parse argument name"); + return -1; + } + + if (*c->in != '}') { + err("expecting closing bracket"); + return -1; + } + + advance_in(c, 1); /* Skip closing braket */ + + /* Find argument by name in spec */ + struct ev_arg *arg = ev_spec_find_arg(spec, argname); + + if (arg == NULL) { + err("cannot find argument %s", argname); + return -1; + } + + /* If there was no custom format, use the default */ + if (infer_fmt) { + if (snprintf(fmt, sizeof(fmt), "%s", type_fmt[arg->type]) >= 64) { + err("format type too long"); + return -1; + } + } + + if (print_arg(arg, fmt, c, ev) != 0) { + err("cannot print argument %s", argname); + return -1; + } + + return 0; +} + +int +ev_spec_print(struct ev_spec *spec, struct emu_ev *ev, char *outbuf, int outlen) +{ + if (outlen <= 0) { + err("buffer has no space"); + return -1; + } + + struct cursor c = { + .in = spec->description, + .out = outbuf, + .len = outlen - 1, /* Leave room for the nil */ + }; + + /* Invariant len >= 0, so the nil character always fits. */ + while (*c.in != '\0') { + if (c.len == 0) { + err("description too long for buffer"); + return -1; + } + + if (*c.in == '%') { + /* Begin format region */ + if (format_region(spec, &c, ev) != 0) { + err("format_region failed"); + return -1; + } + } else { + /* Just copy the character in the output */ + *c.out = *c.in; + c.in++; + advance_out(&c, 1); + } + } + + /* Finish the buffer */ + *c.out = '\0'; + + return 0; +} diff --git a/src/emu/ev_spec.h b/src/emu/ev_spec.h new file mode 100644 index 0000000..a167fe7 --- /dev/null +++ b/src/emu/ev_spec.h @@ -0,0 +1,69 @@ +/* Copyright (c) 2023-2024 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef EV_SPEC_H +#define EV_SPEC_H + +#include +#include +#include "uthash.h" + +struct ev_decl { + const char *signature; + const char *description; +}; + +enum ev_arg_type { + U8 = 0, + U16, + U32, + U64, + I8, + I16, + I32, + I64, + STR, + MAX_TYPE +}; + +#define MAX_ARGS 16 + +struct ev_arg { + size_t size; /* in bytes */ + size_t offset; /* in bytes */ + enum ev_arg_type type; + char name[64]; +}; + +struct ev_spec { + char mcv[4]; + char signature[256]; + int is_jumbo; + int nargs; + struct ev_arg args[MAX_ARGS]; + size_t payload_size; + const char *description; + + UT_hash_handle hh; /* hash map by MCV for model_evspec */ +}; + +/* Helpers for event pairs (with same with). */ +#define PAIR_E(MCV1, MCV2, desc) \ + { MCV1, "enters " desc }, \ + { MCV2, "leaves " desc }, + +#define PAIR_B(MCV1, MCV2, desc) \ + { MCV1, "begins " desc }, \ + { MCV2, "ceases " desc }, + +#define PAIR_S(MCV1, MCV2, desc) \ + { MCV1, "starts " desc }, \ + { MCV2, "stops " desc }, + +struct emu_ev; + +int ev_spec_compile(struct ev_spec *spec, struct ev_decl *decl); +int ev_spec_print(struct ev_spec *spec, struct emu_ev *ev, char *outbuf, int outlen); +struct ev_arg *ev_spec_find_arg(struct ev_spec *spec, const char *name); + +#endif /* EV_SPEC_H */ diff --git a/src/emu/kernel/setup.c b/src/emu/kernel/setup.c index aaa10d6..29c0605 100644 --- a/src/emu/kernel/setup.c +++ b/src/emu/kernel/setup.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2021-2024 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ #include "kernel_priv.h" @@ -6,6 +6,7 @@ #include "common.h" #include "emu.h" #include "emu_prv.h" +#include "ev_spec.h" #include "model.h" #include "model_chan.h" #include "model_cpu.h" @@ -19,9 +20,16 @@ static const char model_name[] = "kernel"; enum { model_id = 'K' }; +static struct ev_decl model_evlist[] = { + { "KO[", "out of CPU" }, + { "KO]", "back to CPU" }, + { NULL, NULL }, +}; + struct model_spec model_kernel = { .name = model_name, .version = "1.0.0", + .evlist = model_evlist, .model = model_id, .create = model_kernel_create, .connect = model_kernel_connect, diff --git a/src/emu/model.c b/src/emu/model.c index 7e053d3..f750784 100644 --- a/src/emu/model.c +++ b/src/emu/model.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2021-2024 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ #include "model.h" @@ -7,6 +7,8 @@ #include "version.h" #include "emu.h" #include "emu_args.h" +#include "model_evspec.h" +#include "ev_spec.h" #include "thread.h" #include "proc.h" @@ -26,13 +28,34 @@ model_register(struct model *model, struct model_spec *spec) return -1; } - if (spec->version == NULL) { - err("model %c missing version", i); + if (model->registered[i]) { + err("model %c already registered", i); return -1; } - if (model->registered[i]) { - err("model %c already registered", i); + if (spec->version == NULL) { + err("model %s missing version", spec->name); + return -1; + } + + if (spec->evlist == NULL) { + err("model %s missing evlist", spec->name); + return -1; + } + + if (spec->evspec != NULL) { + err("model %s evspec must be NULL", spec->name); + return -1; + } + + spec->evspec = calloc(1, sizeof(struct model_evspec)); + if (spec->evspec == NULL) { + err("calloc failed:"); + return -1; + } + + if (model_evspec_init(spec->evspec, spec) != 0) { + err("model_evspec_init failed for model %s", spec->name); return -1; } @@ -80,9 +103,13 @@ model_probe(struct model *model, struct emu *emu) continue; struct model_spec *spec = model->spec[i]; + long nevents = spec->evspec->nevents; - info(" %8s %s '%c'", - spec->name, spec->version, (char) i); + info(" %8s %s '%c' (%ld events)", + spec->name, + spec->version, + (char) i, + nevents); } } @@ -158,6 +185,32 @@ model_event(struct model *model, struct emu *emu, int index) return 0; } +int +model_event_print(struct model *model, struct emu_ev *ev, + char *buf, int buflen) +{ + int index = ev->m; + if (!model->registered[index]) { + err("no model registered for %c", ev->m); + return -1; + } + + struct model_spec *spec = model->spec[index]; + struct ev_spec *es = model_evspec_find(spec->evspec, ev->mcv); + + if (es == NULL) { + err("cannot find event definition for %s", ev->mcv); + return -1; + } + + if (ev_spec_print(es, ev, buf, buflen) < 0) { + err("cannot print event signature for %s", ev->mcv); + return -1; + } + + return 0; +} + int model_finish(struct model *model, struct emu *emu) { diff --git a/src/emu/model.h b/src/emu/model.h index c92ac5a..2112064 100644 --- a/src/emu/model.h +++ b/src/emu/model.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2021-2024 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ #ifndef MODEL_H @@ -7,17 +7,22 @@ #include "common.h" #include "emu_hook.h" struct emu; +struct emu_ev; +struct ev_decl; +struct ev_spec; struct model_spec { const char *name; const char *version; int model; - char *depends; + struct ev_decl *evlist; emu_hook_t *probe; emu_hook_t *create; emu_hook_t *connect; emu_hook_t *event; emu_hook_t *finish; + + struct model_evspec *evspec; }; #define MAX_MODELS 256 @@ -34,6 +39,8 @@ USE_RET int model_probe(struct model *model, struct emu *emu); USE_RET int model_create(struct model *model, struct emu *emu); USE_RET int model_connect(struct model *model, struct emu *emu); USE_RET int model_event(struct model *model, struct emu *emu, int index); +USE_RET int model_event_print(struct model *model, struct emu_ev *ev, + char *buf, int buflen); USE_RET int model_finish(struct model *model, struct emu *emu); USE_RET int model_version_probe(struct model_spec *spec, struct emu *emu); diff --git a/src/emu/model_evspec.c b/src/emu/model_evspec.c new file mode 100644 index 0000000..6b67cb6 --- /dev/null +++ b/src/emu/model_evspec.c @@ -0,0 +1,72 @@ +/* Copyright (c) 2024 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "model_evspec.h" +#include "model.h" +#include "ev_spec.h" +#include + +int +model_evspec_init(struct model_evspec *evspec, struct model_spec *spec) +{ + memset(evspec, 0, sizeof(struct model_evspec)); + + /* Count events */ + for (long i = 0; spec->evlist[i].signature != NULL; i++) + evspec->nevents++; + + if (evspec->nevents == 0) { + err("no events defined in model %s", spec->name); + return -1; + } + + /* Preallocate a contiguous map, as we know the size */ + evspec->alloc = calloc(evspec->nevents, sizeof(struct ev_spec)); + if (evspec->alloc == NULL) { + err("calloc failed:"); + return -1; + } + + for (long i = 0; spec->evlist[i].signature != NULL; i++) { + struct ev_decl *evdecl = &spec->evlist[i]; + struct ev_spec *s = &evspec->alloc[i]; + + if (ev_spec_compile(s, evdecl) != 0) { + err("cannot compile event declaration %s", + evdecl->signature); + return -1; + } + + /* Ensure is not duplicated */ + struct ev_spec *dup = model_evspec_find(evspec, s->mcv); + + if (dup != NULL) { + err("duplicated MCV %s in model %s", + evdecl->signature, spec->name); + return -1; + } + + /* Ensure the model character in the declaration matches + * the registered model */ + if (s->mcv[0] != spec->model) { + err("bad MCV '%s' for model %s, should start with '%c'", + evdecl->signature, + spec->name, + spec->model); + return -1; + } + + + HASH_ADD_STR(evspec->spec, mcv, s); + } + + return 0; +} + +struct ev_spec * +model_evspec_find(struct model_evspec *evspec, char mcv[4]) +{ + struct ev_spec *s = NULL; + HASH_FIND_STR(evspec->spec, mcv, s); + return s; +} diff --git a/src/emu/model_evspec.h b/src/emu/model_evspec.h new file mode 100644 index 0000000..d775a1c --- /dev/null +++ b/src/emu/model_evspec.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2024 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#ifndef MODEL_EVSPEC_H +#define MODEL_EVSPEC_H + +#include "common.h" + +struct model_spec; +struct ev_spec; + +struct model_evspec { + /* Hash table indexed by MCV */ + struct ev_spec *spec; + long nevents; + + /* Contiguous memory for allocated table */ + struct ev_spec *alloc; +}; + +USE_RET int model_evspec_init(struct model_evspec *evspec, struct model_spec *spec); +USE_RET struct ev_spec *model_evspec_find(struct model_evspec *evspec, char mcv[4]); + +#endif /* MODEL_EVSPEC_H */ diff --git a/src/emu/mpi/setup.c b/src/emu/mpi/setup.c index 1cda0d7..9fc4acf 100644 --- a/src/emu/mpi/setup.c +++ b/src/emu/mpi/setup.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2023 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2023-2024 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ #include "mpi_priv.h" @@ -8,6 +8,7 @@ #include "emu.h" #include "emu_args.h" #include "emu_prv.h" +#include "ev_spec.h" #include "extend.h" #include "model.h" #include "model_chan.h" @@ -24,9 +25,78 @@ static const char model_name[] = "mpi"; enum { model_id = 'M' }; +static struct ev_decl model_evlist[] = { + PAIR_E("MUf", "MUF", "MPI_Finalize()") + PAIR_E("MUi", "MUI", "MPI_Init()") + PAIR_E("MUt", "MUT", "MPI_Init_thread()") + + PAIR_E("MW[", "MW]", "MPI_Wait()") + PAIR_E("MWa", "MWA", "MPI_Waitall()") + PAIR_E("MWs", "MWS", "MPI_Waitsome()") + PAIR_E("MWy", "MWY", "MPI_Waitany()") + + PAIR_E("MT[", "MT]", "MPI_Test()") + PAIR_E("MTa", "MTA", "MPI_Testall()") + PAIR_E("MTy", "MTY", "MPI_Testany()") + PAIR_E("MTs", "MTS", "MPI_Testsome()") + + PAIR_E("MS[", "MS]", "MPI_Send()") + PAIR_E("MSb", "MSB", "MPI_Bsend()") + PAIR_E("MSr", "MSR", "MPI_Rsend()") + PAIR_E("MSs", "MSS", "MPI_Ssend()") + + PAIR_E("MR[", "MR]", "MPI_Recv()") + PAIR_E("MRs", "MRS", "MPI_Sendrecv()") + PAIR_E("MRo", "MRO", "MPI_Sendrecv_replace()") + + PAIR_E("MAg", "MAG", "MPI_Allgather()") + PAIR_E("MAr", "MAR", "MPI_Allreduce()") + PAIR_E("MAa", "MAA", "MPI_Alltoall()") + + PAIR_E("MCb", "MCB", "MPI_Barrier()") + PAIR_E("MCe", "MCE", "MPI_Exscan()") + PAIR_E("MCs", "MCS", "MPI_Scan()") + + PAIR_E("MDb", "MDB", "MPI_Bcast()") + PAIR_E("MDg", "MDG", "MPI_Gather()") + PAIR_E("MDs", "MDS", "MPI_Scatter()") + + PAIR_E("ME[", "ME]", "MPI_Reduce()") + PAIR_E("MEs", "MES", "MPI_Reduce_scatter()") + PAIR_E("MEb", "MEB", "MPI_Reduce_scatter_block()") + + PAIR_E("Ms[", "Ms]", "MPI_Isend()") + PAIR_E("Msb", "MsB", "MPI_Ibsend()") + PAIR_E("Msr", "MsR", "MPI_Irsend()") + PAIR_E("Mss", "MsS", "MPI_Issend()") + + PAIR_E("Mr[", "Mr]", "MPI_Irecv()") + PAIR_E("Mrs", "MrS", "MPI_Isendrecv()") + PAIR_E("Mro", "MrO", "MPI_Isendrecv_replace()") + + PAIR_E("Mag", "MaG", "MPI_Iallgather()") + PAIR_E("Mar", "MaR", "MPI_Iallreduce()") + PAIR_E("Maa", "MaA", "MPI_Ialltoall()") + + PAIR_E("Mcb", "McB", "MPI_Ibarrier()") + PAIR_E("Mce", "McE", "MPI_Iexscan()") + PAIR_E("Mcs", "McS", "MPI_Iscan()") + + PAIR_E("Mdb", "MdB", "MPI_Ibcast()") + PAIR_E("Mdg", "MdG", "MPI_Igather()") + PAIR_E("Mds", "MdS", "MPI_Iscatter()") + + PAIR_E("Me[", "Me]", "MPI_Ireduce()") + PAIR_E("Mes", "MeS", "MPI_Ireduce_scatter()") + PAIR_E("Meb", "MeB", "MPI_Ireduce_scatter_block()") + + { NULL, NULL }, +}; + struct model_spec model_mpi = { .name = model_name, .version = "1.0.0", + .evlist = model_evlist, .model = model_id, .create = model_mpi_create, .connect = model_mpi_connect, diff --git a/src/emu/nanos6/setup.c b/src/emu/nanos6/setup.c index 9ba0226..0cbb4ea 100644 --- a/src/emu/nanos6/setup.c +++ b/src/emu/nanos6/setup.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2021-2024 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ #include "nanos6_priv.h" @@ -10,6 +10,7 @@ #include "emu.h" #include "emu_args.h" #include "emu_prv.h" +#include "ev_spec.h" #include "extend.h" #include "model.h" #include "model_chan.h" @@ -31,9 +32,56 @@ static const char model_name[] = "nanos6"; enum { model_id = '6' }; +static struct ev_decl model_evlist[] = { + { "6Yc+(u32 typeid, str label)", "creates task type %{typeid} with label \"%{label}\"" }, + { "6Tc(u32 taskid, u32 typeid)", "creates task %{taskid} with type %{typeid}" }, + { "6Tx(u32 taskid)", "executes the task %{taskid}" }, + { "6Te(u32 taskid)", "ends the task %{taskid}" }, + { "6Tp(u32 taskid)", "pauses the task %{taskid}" }, + { "6Tr(u32 taskid)", "resumes the task %{taskid}" }, + PAIR_E("6W[", "6W]", "worker main loop, looking for tasks") + PAIR_B("6Wt", "6WT", "handling a task via handleTask()") + PAIR_B("6Ww", "6WW", "switching to another worker via switchTo()") + /* FIXME: 6Wm and 6WM not instrumented by Nanos6 */ + PAIR_B("6Wm", "6WM", "migrating the current worker to another CPU") + PAIR_B("6Ws", "6WS", "suspending the worker via suspend()") + PAIR_B("6Wr", "6WR", "resuming another worker via resume()") + PAIR_E("6Wg", "6WG", "sponge mode (absorbing system noise)") + { "6W*", "signals another worker to wake up" }, + { "6Pp", "sets progress state to Progressing" }, + { "6Pr", "sets progress state to Resting" }, + { "6Pa", "sets progress state to Absorbing" }, + PAIR_B("6C[", "6C]", "creating a new task") + PAIR_B("6U[", "6U]", "submitting a task via submitTask()") + PAIR_B("6F[", "6F]", "spawning a function via spawnFunction()") + PAIR_E("6t[", "6t]", "the task body") + /* FIXME: Deprecated, remove */ + PAIR_B("6O[", "6O]", "running the task body as taskfor collaborator") + PAIR_S("6Ma", "6MA", "allocating memory") + PAIR_S("6Mf", "6MF", "freeing memory") + PAIR_B("6Dr", "6DR", "registration of task dependencies") + PAIR_B("6Du", "6DU", "unregistration of task dependencies") + PAIR_B("6S[", "6S]", "scheduler serving mode") + PAIR_B("6Sa", "6SA", "submitting a ready task via addReadyTask()") + PAIR_B("6Sp", "6SP", "processing ready tasks via processReadyTasks()") + { "6S@", "self assigns itself a task" }, + { "6Sr", "receives a task from another thread" }, + { "6Ss", "sends a task to another thread" }, + PAIR_B("6Bb", "6BB", "blocking the current task") + PAIR_B("6Bu", "6BU", "unblocking a task") + PAIR_E("6Bw", "6BW", "a task wait") + PAIR_E("6Bf", "6BF", "a wait for") + PAIR_B("6He", "6HE", "execution as external thread") + PAIR_B("6Hw", "6HW", "execution as worker") + PAIR_B("6Hl", "6HL", "execution as leader") + PAIR_B("6Hm", "6HM", "execution as main thread") + { NULL, NULL }, +}; + struct model_spec model_nanos6 = { .name = model_name, .version = "1.0.0", + .evlist = model_evlist, .model = model_id, .create = model_nanos6_create, .connect = model_nanos6_connect, diff --git a/src/emu/nodes/setup.c b/src/emu/nodes/setup.c index dbc10a7..8764532 100644 --- a/src/emu/nodes/setup.c +++ b/src/emu/nodes/setup.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2021-2024 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ #include "nodes_priv.h" @@ -8,6 +8,7 @@ #include "emu.h" #include "emu_args.h" #include "emu_prv.h" +#include "ev_spec.h" #include "extend.h" #include "model.h" #include "model_chan.h" @@ -24,9 +25,22 @@ static const char model_name[] = "nodes"; enum { model_id = 'D' }; +static struct ev_decl model_evlist[] = { + PAIR_B("DR[", "DR]", "registering task accesses") + PAIR_B("DU[", "DU]", "unregistering task accesses") + PAIR_E("DW[", "DW]", "a blocking condition (waiting for an If0 task)") + PAIR_B("DI[", "DI]", "the inline execution of an If0 task") + PAIR_E("DT[", "DT]", "a taskwait") + PAIR_B("DC[", "DC]", "creating a task") + PAIR_B("DS[", "DS]", "submitting a task") + PAIR_B("DP[", "DP]", "spawning a function") + { NULL, NULL }, +}; + struct model_spec model_nodes = { .name = model_name, .version = "1.0.0", + .evlist = model_evlist, .model = model_id, .create = model_nodes_create, .connect = model_nodes_connect, diff --git a/src/emu/nosv/setup.c b/src/emu/nosv/setup.c index 07b862f..9dc2069 100644 --- a/src/emu/nosv/setup.c +++ b/src/emu/nosv/setup.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2021-2024 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ #include "nosv_priv.h" @@ -9,6 +9,7 @@ #include "emu.h" #include "emu_args.h" #include "emu_prv.h" +#include "ev_spec.h" #include "extend.h" #include "model.h" #include "model_chan.h" @@ -29,9 +30,48 @@ static const char model_name[] = "nosv"; enum { model_id = 'V' }; +static struct ev_decl model_evlist[] = { + { "VTc(u32 taskid, u32 typeid)", "creates task %{taskid} with type %{typeid}" }, + { "VTx(u32 taskid)", "executes the task %{taskid}" }, + { "VTe(u32 taskid)", "ends the task %{taskid}" }, + { "VTp(u32 taskid)", "pauses the task %{taskid}" }, + { "VTr(u32 taskid)", "resumes the task %{taskid}" }, + + { "VYc+(u32 typeid, str label)", "creates task type %{typeid} with label \"%{label}\"" }, + + { "VSr", "receives a task from another thread" }, + { "VSs", "sends a task to another thread" }, + { "VS@", "self assigns itself a task" }, + { "VSh", "enters the hungry state, waiting for work" }, + { "VSf", "is no longer hungry" }, + PAIR_E("VS[", "VS]", "scheduler server mode") + + PAIR_S("VU[", "VU]", "submitting a task") + PAIR_S("VMa", "VMA", "allocating memory") + PAIR_S("VMf", "VMF", "freeing memory") + + PAIR_E("VAr", "VAR", "nosv_create()") + PAIR_E("VAd", "VAD", "nosv_destroy()") + PAIR_E("VAs", "VAS", "nosv_submit()") + PAIR_E("VAp", "VAP", "nosv_pause()") + PAIR_E("VAy", "VAY", "nosv_yield()") + PAIR_E("VAw", "VAW", "nosv_waitfor()") + PAIR_E("VAc", "VAC", "nosv_schedpoint()") + + /* FIXME: VHA and VHa are not subsystems */ + { "VHa", "enters nosv_attach()" }, + { "VHA", "leaves nosv_dettach()" }, + + PAIR_B("VHw", "VHW", "execution as worker") + PAIR_B("VHd", "VHD", "execution as delegate") + + { NULL, NULL }, +}; + struct model_spec model_nosv = { .name = model_name, .version = "1.0.0", + .evlist = model_evlist, .model = model_id, .create = model_nosv_create, .connect = model_nosv_connect, diff --git a/src/emu/ovni/setup.c b/src/emu/ovni/setup.c index a6e8795..3a8ea48 100644 --- a/src/emu/ovni/setup.c +++ b/src/emu/ovni/setup.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2021-2024 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ #include "ovni_priv.h" @@ -6,6 +6,7 @@ #include "common.h" #include "emu.h" #include "emu_prv.h" +#include "ev_spec.h" #include "model.h" #include "model_chan.h" #include "model_cpu.h" @@ -20,9 +21,29 @@ static const char model_name[] = "ovni"; enum { model_id = 'O' }; +static struct ev_decl model_evlist[] = { + { "OAr(i32 cpu, i32 tid)", "changes the affinity of thread %{tid} to CPU %{cpu}" }, + { "OAs(i32 cpu)", "switches it's own affinity to the CPU %{cpu}" }, + { "OB.", "emits a burst event to measure latency" }, + { "OHC(i32 cpu, u64 tag)", "creates a new thread on CPU %{cpu} with tag %#llx{tag}" }, + { "OHc", "enters the Cooling state (about to be paused)" }, + { "OHe", "ends the execution" }, + { "OHp", "pauses the execution" }, + { "OHr", "resumes the execution" }, + { "OHw", "enters the Warming state (about to be running)" }, + { "OHx(i32 cpu, i32 tid, u64 tag)", "begins the execution on CPU %{cpu} created from %{tid} with tag %#llx{tag}" }, + { "OCn(i32 cpu)", "informs there are %{cpu} CPUs" }, + + PAIR_B("OF[", "OF]", "flushing events to disk") + PAIR_E("OU[", "OU]", "unordered event region") + + { NULL, NULL }, +}; + struct model_spec model_ovni = { .name = model_name, .version = "1.0.0", + .evlist = model_evlist, .model = model_id, .create = model_ovni_create, .connect = model_ovni_connect, diff --git a/src/emu/ovnidump.c b/src/emu/ovnidump.c index 5eb1155..550dcc4 100644 --- a/src/emu/ovnidump.c +++ b/src/emu/ovnidump.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2021-2023 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2021-2024 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ #include @@ -7,37 +7,42 @@ #include #include "common.h" #include "emu_ev.h" +#include "model.h" +#include "models.h" #include "ovni.h" #include "player.h" #include "stream.h" #include "trace.h" char *tracedir; +int hex_mode = 0; static void -emit(struct player *player) +emit(struct model *model, struct player *player) { - static int64_t c = 0; - struct emu_ev *ev = player_ev(player); struct stream *stream = player_stream(player); - /* Use raw clock in the ovni event */ - int64_t rel = stream->deltaclock; - c = ev->rclock; - - printf("%s %10ld %+10ld %c%c%c", - stream->relpath, - c, - rel, + printf("%10ld %c%c%c %s ", + ev->rclock, ev->m, ev->c, - ev->v); + ev->v, + stream->relpath); - if (ev->has_payload) { - printf(" "); - for (size_t i = 0; i < ev->payload_size; i++) - printf(":%02x", ev->payload->u8[i]); + if (hex_mode) { + if (ev->has_payload) { + for (size_t i = 0; i < ev->payload_size; i++) + printf(":%02x", ev->payload->u8[i]); + } + } else { + char buf[1024]; + if (model_event_print(model, ev, buf, 1024) < 0) { + err("failed to decode event %s", ev->mcv); + printf("UNKNOWN"); + } else { + printf("%s", buf); + } } printf("\n"); @@ -46,7 +51,7 @@ emit(struct player *player) static void usage(void) { - rerr("Usage: ovnidump DIR\n"); + rerr("Usage: ovnidump [-x] DIR\n"); rerr("\n"); rerr("Dumps the events of the trace to the standard output.\n"); rerr("\n"); @@ -62,8 +67,11 @@ parse_args(int argc, char *argv[]) { int opt; - while ((opt = getopt(argc, argv, "h")) != -1) { + while ((opt = getopt(argc, argv, "hx")) != -1) { switch (opt) { + case 'x': + hex_mode = 1; + break; case 'h': default: /* '?' */ usage(); @@ -85,6 +93,15 @@ main(int argc, char *argv[]) parse_args(argc, argv); + struct model model; + model_init(&model); + + /* Register all the models */ + if (models_register(&model) != 0) { + err("failed to register models"); + return -1; + } + struct trace *trace = calloc(1, sizeof(struct trace)); if (trace == NULL) { @@ -111,7 +128,7 @@ main(int argc, char *argv[]) int ret; while ((ret = player_step(player)) == 0) { - emit(player); + emit(&model, player); } /* Error happened */ diff --git a/src/emu/tampi/setup.c b/src/emu/tampi/setup.c index 8fda1c4..d243d72 100644 --- a/src/emu/tampi/setup.c +++ b/src/emu/tampi/setup.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2023 Barcelona Supercomputing Center (BSC) +/* Copyright (c) 2023-2024 Barcelona Supercomputing Center (BSC) * SPDX-License-Identifier: GPL-3.0-or-later */ #include "tampi_priv.h" @@ -8,6 +8,7 @@ #include "emu.h" #include "emu_args.h" #include "emu_prv.h" +#include "ev_spec.h" #include "extend.h" #include "model.h" #include "model_chan.h" @@ -24,9 +25,26 @@ static const char model_name[] = "tampi"; enum { model_id = 'T' }; +static struct ev_decl model_evlist[] = { + PAIR_S("TCi", "TCI", "issuing a non-blocking communication operation") + PAIR_S("TGc", "TGC", "checking pending requests from the global array") + PAIR_E("TLi", "TLI", "the library code at an API function") + PAIR_E("TLp", "TLP", "the library code at a polling function") + PAIR_S("TQa", "TQA", "adding a ticket/requests to a queue") + PAIR_S("TQt", "TQT", "transferring tickets/requests from queues to global array") + PAIR_S("TRc", "TRC", "processsing a completed request") + PAIR_S("TRt", "TRT", "testing a single request with MPI_Test") + PAIR_S("TRa", "TRA", "testing several requests with MPI_Testall") + PAIR_S("TRs", "TRS", "testing several requests with MPI_Testsome") + PAIR_S("TTc", "TTC", "creating a ticket linked to a set of requests and a task") + PAIR_S("TTw", "TTW", "waiting for a ticket completion") + { NULL, NULL }, +}; + struct model_spec model_tampi = { .name = model_name, .version = "1.0.0", + .evlist = model_evlist, .model = model_id, .create = model_tampi_create, .connect = model_tampi_connect, diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index d2ac78f..9fb056f 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (c) 2022-2023 Barcelona Supercomputing Center (BSC) +# Copyright (c) 2022-2024 Barcelona Supercomputing Center (BSC) # SPDX-License-Identifier: GPL-3.0-or-later function(unit_test) @@ -21,3 +21,4 @@ unit_test(version.c) unit_test(path.c) unit_test(sort.c) unit_test(sort_replace.c) +unit_test(ev_spec.c) diff --git a/test/unit/ev_spec.c b/test/unit/ev_spec.c new file mode 100644 index 0000000..f325077 --- /dev/null +++ b/test/unit/ev_spec.c @@ -0,0 +1,162 @@ +/* Copyright (c) 2023-2024 Barcelona Supercomputing Center (BSC) + * SPDX-License-Identifier: GPL-3.0-or-later */ + +#include "ev_spec.h" + +#include "unittest.h" +#include "emu_ev.h" +#include + +struct testcase { + struct ev_decl decl; + uint8_t payload[256]; + int ret_compile; + int ret_print; + char *output; +}; + +static void +test_format(void) +{ + struct testcase cases[] = { +/* Bad compile tests */ + { + /* Test bad MCV */ + .decl = { "O x", "" }, + .ret_compile = -1, + }, + { + /* Jumbo but no argument (nonsense) */ + .decl = { "OHx+", "" }, + .ret_compile = -1, + }, + { + /* Empty arguments */ + .decl = { "OHx()", "" }, + .ret_compile = -1, + }, + { + /* Missing opening parenthesis */ + .decl = { "OHx)", "" }, + .ret_compile = -1, + }, + { + /* Typo, 4 MCV char */ + .decl = { "OOHx", "" }, + .ret_compile = -1, + }, + { + /* Typo, 2 MCV char */ + .decl = { "Ox", "" }, + .ret_compile = -1, + }, + { + /* Typo, 2 MCV char with args */ + .decl = { "Ox(i32 cpu)", "" }, + .ret_compile = -1, + }, +/* Bad printing tests */ + { + /* Test missing argument */ + .decl = { "OHx", "hi missing %{cpu}" }, + .ret_print = -1, + }, + { + /* Test missing closing bracket */ + .decl = { "OHx(i32 cpu)", "hi missing %{cpu" }, + .ret_print = -1, + }, + { + /* Test using parenthesis instead */ + .decl = { "OHx(i32 cpu)", "hi missing %(cpu)" }, + .ret_print = -1, + }, +/* Good tests */ + { + /* Test arguments in normal event */ + .decl = { + "OAr(i32 cpu, i32 tid)", + "changes the affinity of thread %{tid} to CPU %{cpu}" + }, + .payload = { + 0x01, 0x00, 0x00, 0x00, /* CPU */ + 0x02, 0x00, 0x00, 0x00, /* TID */ + }, + .output = "changes the affinity of thread 2 to CPU 1", + }, + { + /* Test custom printf format */ + .decl = { + "OAr(i32 cpu)", + "we like the CPU %08d{cpu} well padded" + }, + .payload = { + 0x03, 0x00, 0x00, 0x00, /* CPU */ + }, + .output = "we like the CPU 00000003 well padded", + }, + { + /* Test i32 in jumbo */ + .decl = { + "ooo+(i32 cpu)", "welcome to CPU %{cpu}" + }, + .payload = { + 0x00, 0x00, 0x00, 0x00, /* jumbo size, + ignored */ + 0x05, 0x00, 0x00, 0x00, /* cpu */ + }, + .output = "welcome to CPU 5", + }, + { + /* Test string in jumbo */ + .decl = { + "ooo+(str name)", "welcome %{name}!" + }, + .payload = { + 0x00, 0x00, 0x00, 0x00, /* jumbo size, + ignored */ + 'a', 'l', 'i', 'e', 'n', '\0', /* name */ + }, + .output = "welcome alien!", + }, + }; + + char buf[1024]; + int bufsize = 1024; + int n = sizeof(cases) / sizeof(cases[0]); + for (int i = 0; i < n; i++) { + struct testcase *c = &cases[i]; + struct ev_spec spec = {0}; + struct ev_decl *decl = &c->decl; + struct emu_ev ev = { + .payload = (union ovni_ev_payload *) &c->payload + }; + if (ev_spec_compile(&spec, decl) != c->ret_compile) + die("compile return mismatch for %s", decl->signature); + + /* Only print if compiled worked */ + if (c->ret_compile == 0) { + if (ev_spec_print(&spec, &ev, buf, bufsize) != c->ret_print) + die("print return mismatch for %s", decl->signature); + + /* Only check buffer if print worked */ + if (c->ret_print == 0) { + if (strcmp(buf, c->output) != 0) + die("different output: '%s' != '%s'", + buf, c->output); + else + info("same output"); + } + } + + info("case %d/%d OK", i, n); + } +} + + +int main(void) +{ + test_format(); + + return 0; +}