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.
319 lines
6.4 KiB
C
319 lines
6.4 KiB
C
/* Copyright (c) 2021-2024 Barcelona Supercomputing Center (BSC)
|
|
* SPDX-License-Identifier: GPL-3.0-or-later */
|
|
|
|
#include "model.h"
|
|
#include <string.h>
|
|
#include "common.h"
|
|
#include "version.h"
|
|
#include "emu.h"
|
|
#include "emu_args.h"
|
|
#include "model_evspec.h"
|
|
#include "ev_spec.h"
|
|
#include "thread.h"
|
|
#include "proc.h"
|
|
|
|
void
|
|
model_init(struct model *model)
|
|
{
|
|
memset(model, 0, sizeof(struct model));
|
|
}
|
|
|
|
int
|
|
model_register(struct model *model, struct model_spec *spec)
|
|
{
|
|
int i = spec->model;
|
|
|
|
if (spec->name == NULL) {
|
|
err("model %c missing name", i);
|
|
return -1;
|
|
}
|
|
|
|
if (model->registered[i]) {
|
|
err("model %c already registered", i);
|
|
return -1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
model->spec[i] = spec;
|
|
model->registered[i] = 1;
|
|
|
|
dbg("registered model %c", (char) i);
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
model_probe(struct model *model, struct emu *emu)
|
|
{
|
|
int nenabled = 0;
|
|
for (int i = 0; i < MAX_MODELS; i++) {
|
|
if (!model->registered[i])
|
|
continue;
|
|
|
|
struct model_spec *spec = model->spec[i];
|
|
if (spec->probe == NULL)
|
|
continue;
|
|
|
|
int ret = spec->probe(emu);
|
|
if (ret < 0) {
|
|
err("probe failed for model '%c'", (char) i);
|
|
return -1;
|
|
}
|
|
|
|
/* Zero is disabled */
|
|
if (ret > 0 || emu->args.enable_all_models) {
|
|
model->enabled[i] = 1;
|
|
nenabled++;
|
|
}
|
|
}
|
|
|
|
if (nenabled == 0) {
|
|
warn("no models enabled");
|
|
} else {
|
|
if (emu->args.enable_all_models)
|
|
info("all %d models are enabled (-a): ", nenabled);
|
|
else
|
|
info("the following %d models are enabled: ", nenabled);
|
|
for (int i = 0; i < MAX_MODELS; i++) {
|
|
if (!model->enabled[i])
|
|
continue;
|
|
|
|
struct model_spec *spec = model->spec[i];
|
|
long nevents = spec->evspec->nevents;
|
|
|
|
info(" %8s %s '%c' (%ld events)",
|
|
spec->name,
|
|
spec->version,
|
|
(char) i,
|
|
nevents);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
model_create(struct model *model, struct emu *emu)
|
|
{
|
|
for (int i = 0; i < MAX_MODELS; i++) {
|
|
if (!model->enabled[i])
|
|
continue;
|
|
|
|
struct model_spec *spec = model->spec[i];
|
|
if (spec->create == NULL)
|
|
continue;
|
|
|
|
if (spec->create(emu) != 0) {
|
|
err("create failed for model '%c'", (char) i);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
model_connect(struct model *model, struct emu *emu)
|
|
{
|
|
for (int i = 0; i < MAX_MODELS; i++) {
|
|
if (!model->enabled[i])
|
|
continue;
|
|
|
|
struct model_spec *spec = model->spec[i];
|
|
if (spec->connect == NULL)
|
|
continue;
|
|
|
|
if (spec->connect(emu) != 0) {
|
|
err("connect failed for model '%c'", (char) i);
|
|
return -1;
|
|
}
|
|
|
|
dbg("connect for model %c ok", (char) i);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
model_event(struct model *model, struct emu *emu, int index)
|
|
{
|
|
struct model_spec *spec = model->spec[index];
|
|
|
|
if (!model->registered[index]) {
|
|
err("model not registered for event %s", emu->ev->mcv);
|
|
return -1;
|
|
}
|
|
|
|
if (!model->enabled[index]) {
|
|
err("model %s not enabled for event %s",
|
|
spec->name, emu->ev->mcv);
|
|
info("missing call to ovni_thread_require(\"%s\", \"%s\")?",
|
|
spec->name, spec->version);
|
|
return -1;
|
|
}
|
|
|
|
if (spec->event == NULL)
|
|
return 0;
|
|
|
|
if (spec->event(emu) != 0) {
|
|
err("event() failed for '%s' model", spec->name);
|
|
return -1;
|
|
}
|
|
|
|
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)
|
|
{
|
|
int ret = 0;
|
|
for (int i = 0; i < MAX_MODELS; i++) {
|
|
if (!model->enabled[i])
|
|
continue;
|
|
|
|
struct model_spec *spec = model->spec[i];
|
|
if (spec->finish == NULL)
|
|
continue;
|
|
|
|
if (spec->finish(emu) != 0) {
|
|
err("finish failed for model '%c'", (char) i);
|
|
ret = -1;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int
|
|
should_enable(int have[3], struct model_spec *spec, struct thread *t)
|
|
{
|
|
static int compat = 0;
|
|
|
|
/* Enable all models if we are in compatibility model. Don't check other
|
|
* threads metadata */
|
|
if (compat)
|
|
return 1;
|
|
|
|
if (t->meta == NULL) {
|
|
err("missing metadata for thread %s", t->id);
|
|
return -1;
|
|
}
|
|
|
|
JSON_Object *require = json_object_dotget_object(t->meta, "ovni.require");
|
|
if (require == NULL) {
|
|
warn("missing 'ovni.require' key in thread %s", t->id);
|
|
warn("loading trace in compatibility mode");
|
|
warn("all models will be enabled (expect slowdown)");
|
|
warn("use ovni_thread_require() to enable only required models");
|
|
compat = 1;
|
|
return 1;
|
|
}
|
|
|
|
/* May not have the current model */
|
|
const char *req_version = json_object_get_string(require, spec->name);
|
|
if (req_version == NULL)
|
|
return 0;
|
|
|
|
int want[3];
|
|
if (version_parse(req_version, want) != 0) {
|
|
err("cannot parse version %s from thread %s", req_version, t->id);
|
|
return -1;
|
|
}
|
|
|
|
if (!version_is_compatible(want, have)) {
|
|
err("unsupported %s model version (want %s, have %s) in %s",
|
|
spec->name, req_version, spec->version,
|
|
t->id);
|
|
return -1;
|
|
}
|
|
|
|
/* Compatible */
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
model_version_probe(struct model_spec *spec, struct emu *emu)
|
|
{
|
|
int enable = 0;
|
|
int have[3];
|
|
|
|
if (version_parse(spec->version, have) != 0) {
|
|
err("cannot parse version %s", spec->version);
|
|
return -1;
|
|
}
|
|
|
|
char path[128];
|
|
if (snprintf(path, 128, "%s.version", spec->name) >= 128)
|
|
die("model %s name too long", spec->name);
|
|
|
|
/* Find a stream with an model name key */
|
|
struct system *sys = &emu->system;
|
|
for (struct thread *t = sys->threads; t; t = t->gnext) {
|
|
int ret = should_enable(have, spec, t);
|
|
|
|
if (ret < 0) {
|
|
err("cannot determine if model %s must be enabled", spec->name);
|
|
return -1;
|
|
} else if (ret != 0) {
|
|
enable = 1;
|
|
/* Check the rest of threads */
|
|
}
|
|
}
|
|
|
|
if (enable) {
|
|
dbg("model %s enabled", spec->name);
|
|
} else {
|
|
dbg("model %s disabled", spec->name);
|
|
}
|
|
|
|
return enable;
|
|
}
|