From 1792c650ec9405fb79f0a00392a430becbc8a35e Mon Sep 17 00:00:00 2001 From: Rodrigo Arias Date: Fri, 12 Jan 2024 15:19:23 +0100 Subject: [PATCH] Add definitions for emulator events Implements a small language parser to define the emulator events. The event specification is parsed at emulation (when the emulator runs). The ovnidump output now prints the events with the arguments formatted as given in the event description. It also introduces some consistency checks over the event MCVs, which must begin with the model identifier and cannot be duplicated. --- src/emu/CMakeLists.txt | 4 +- src/emu/ev_spec.c | 537 +++++++++++++++++++++++++++++++++++++++ src/emu/ev_spec.h | 69 +++++ src/emu/kernel/setup.c | 10 +- src/emu/model.c | 67 ++++- src/emu/model.h | 11 +- src/emu/model_evspec.c | 72 ++++++ src/emu/model_evspec.h | 24 ++ src/emu/mpi/setup.c | 72 +++++- src/emu/nanos6/setup.c | 50 +++- src/emu/nodes/setup.c | 16 +- src/emu/nosv/setup.c | 42 ++- src/emu/ovni/setup.c | 23 +- src/emu/ovnidump.c | 57 +++-- src/emu/tampi/setup.c | 20 +- test/unit/CMakeLists.txt | 3 +- test/unit/ev_spec.c | 162 ++++++++++++ 17 files changed, 1201 insertions(+), 38 deletions(-) create mode 100644 src/emu/ev_spec.c create mode 100644 src/emu/ev_spec.h create mode 100644 src/emu/model_evspec.c create mode 100644 src/emu/model_evspec.h create mode 100644 test/unit/ev_spec.c 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; +}