Message ID | fa02142d349ceb6c95e80301a7f5c57ae5df6329.1733063076.git.neither@nut.email (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | tcg-plugins: add hooks for discontinuities | expand |
On 12/2/24 11:26, Julian Ganz wrote: > We recently introduced new plugin API for registration of discontinuity > related callbacks. This change introduces a minimal plugin showcasing > the new API. It simply counts the occurances of interrupts, exceptions > and host calls per CPU and reports the counts when exitting. > --- > contrib/plugins/meson.build | 3 +- > contrib/plugins/traps.c | 96 +++++++++++++++++++++++++++++++++++++ > 2 files changed, 98 insertions(+), 1 deletion(-) > create mode 100644 contrib/plugins/traps.c > > diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build > index 63a32c2b4f..9a3015e1c1 100644 > --- a/contrib/plugins/meson.build > +++ b/contrib/plugins/meson.build > @@ -1,5 +1,6 @@ > contrib_plugins = ['bbv', 'cache', 'cflow', 'drcov', 'execlog', 'hotblocks', > - 'hotpages', 'howvec', 'hwprofile', 'ips', 'stoptrigger'] > + 'hotpages', 'howvec', 'hwprofile', 'ips', 'stoptrigger', > + 'traps'] > if host_os != 'windows' > # lockstep uses socket.h > contrib_plugins += 'lockstep' > diff --git a/contrib/plugins/traps.c b/contrib/plugins/traps.c > new file mode 100644 > index 0000000000..ecd4beac5f > --- /dev/null > +++ b/contrib/plugins/traps.c > @@ -0,0 +1,96 @@ > +/* > + * Copyright (C) 2024, Julian Ganz <neither@nut.email> > + * > + * Traps - count traps > + * > + * License: GNU GPL, version 2 or later. > + * See the COPYING file in the top-level directory. > + */ > + > +#include <stdio.h> > + > +#include <qemu-plugin.h> > + > +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; > + > +typedef struct { > + uint64_t interrupts; > + uint64_t exceptions; > + uint64_t hostcalls; > + bool active; The active field can be removed, as you can query qemu_plugin_num_vcpus() to know (dynamically) how many vcpus were created. > +} TrapCounters; > + > +static struct qemu_plugin_scoreboard *traps; > +static size_t max_vcpus; > + > +static void vcpu_init(qemu_plugin_id_t id, unsigned int vcpu_index) > +{ > + TrapCounters* rec = qemu_plugin_scoreboard_find(traps, vcpu_index); > + rec->active = true; > +} Once active is removed, this function can be removed too. > + > +static void vcpu_discon(qemu_plugin_id_t id, unsigned int vcpu_index, > + enum qemu_plugin_discon_type type, uint64_t from_pc, > + uint64_t to_pc) > +{ > + TrapCounters* rec = qemu_plugin_scoreboard_find(traps, vcpu_index); > + switch (type) { > + case QEMU_PLUGIN_DISCON_INTERRUPT: > + rec->interrupts++; > + break; > + case QEMU_PLUGIN_DISCON_EXCEPTION: > + rec->exceptions++; > + break; > + case QEMU_PLUGIN_DISCON_HOSTCALL: > + rec->hostcalls++; > + break; > + default: > + /* unreachable */ You can use g_assert_not_reached() ensure it's unreachable. We removed assert(0) from the codebase and replaced those with it. > + break; > + } > +} > + > +static void plugin_exit(qemu_plugin_id_t id, void *p) > +{ > + g_autoptr(GString) report; > + report = g_string_new("VCPU, interrupts, exceptions, hostcalls\n"); > + int vcpu; > + > + for (vcpu = 0; vcpu < max_vcpus; vcpu++) { vcpu < qemu_plugin_num_vcpus() > + TrapCounters *rec = qemu_plugin_scoreboard_find(traps, vcpu); > + if (rec->active) { > + g_string_append_printf(report, > + "% 4d, % 10"PRId64", % 10"PRId64", % 10" > + PRId64"\n", > + vcpu, > + rec->interrupts, rec->exceptions, > + rec->hostcalls); > + } > + } > + > + qemu_plugin_outs(report->str); > + qemu_plugin_scoreboard_free(traps); > +} > + > +QEMU_PLUGIN_EXPORT > +int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, > + int argc, char **argv) > +{ > + if (!info->system_emulation) { > + fputs("trap plugin can only be used in system emulation mode.\n", > + stderr); > + return -1; > + } > + > + max_vcpus = info->system.max_vcpus; > + traps = qemu_plugin_scoreboard_new(sizeof(TrapCounters)); > + qemu_plugin_register_vcpu_init_cb(id, vcpu_init); > + qemu_plugin_vcpu_for_each(id, vcpu_init); > + > + qemu_plugin_register_vcpu_discon_cb(id, QEMU_PLUGIN_DISCON_TRAPS, > + vcpu_discon); > + The change from QEMU_PLUGIN_DISCON_TRAPS to QEMU_PLUGIN_DISCON_ALL should be included in this patch, instead of next one. > + qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); > + > + return 0; > +}
Hi Pierrick, December 5, 2024 at 12:14 AM, "Pierrick Bouvier" wrote: > On 12/2/24 11:26, Julian Ganz wrote: > > +typedef struct { > > + uint64_t interrupts; > > + uint64_t exceptions; > > + uint64_t hostcalls; > > + bool active; > > > The active field can be removed, as you can query qemu_plugin_num_vcpus() to know (dynamically) how many vcpus were created. Yes, if the ids of dynamically initialized VCPUs are contiguous. I wasn't sure they really are. And I distinctly remember we originally used some query function and ended up with the maximum number of VCPUs supported rather then those actually used. But that may have been another function, or some unfortunate result of me being too cautious and doing | qemu_plugin_vcpu_for_each(id, vcpu_init); in qemu_plugin_install. > > > > + break; > > + } > > +} > > + > > +static void plugin_exit(qemu_plugin_id_t id, void *p) > > +{ > > + g_autoptr(GString) report; > > + report = g_string_new("VCPU, interrupts, exceptions, hostcalls\n"); > > + int vcpu; > > + > > + for (vcpu = 0; vcpu < max_vcpus; vcpu++) { > > > vcpu < qemu_plugin_num_vcpus() Yes, max_vcpus was introduced as an optimization. If we can rely on all VCPUs with id < qemu_plugin_num_vcpus() having been active at some point this becomes unnecessary. > > + qemu_plugin_register_vcpu_discon_cb(id, QEMU_PLUGIN_DISCON_TRAPS, > > + vcpu_discon); > > + > > > The change from QEMU_PLUGIN_DISCON_TRAPS to QEMU_PLUGIN_DISCON_ALL should be included in this patch, instead of next one. Ah, thanks for pointing that out. I likely fumbled this at some point when rebasing. Regards, Julian Ganz
On 12/5/24 05:00, Julian Ganz wrote: > Hi Pierrick, > > December 5, 2024 at 12:14 AM, "Pierrick Bouvier" wrote: >> On 12/2/24 11:26, Julian Ganz wrote: >>> +typedef struct { >>> + uint64_t interrupts; >>> + uint64_t exceptions; >>> + uint64_t hostcalls; >>> + bool active; >>> >> The active field can be removed, as you can query qemu_plugin_num_vcpus() to know (dynamically) how many vcpus were created. > > Yes, if the ids of dynamically initialized VCPUs are contiguous. I > wasn't sure they really are. And I distinctly remember we originally > used some query function and ended up with the maximum number of VCPUs > supported rather then those actually used. But that may have been > another function, or some unfortunate result of me being too cautious > and doing > It's a valid concern, and the good news is that they are guaranteed to be contiguous. In system mode, we initialize all vcpus *before* executing anything. In user mode, they are spawned when new threads appear. In reality, qemu_plugin_num_vcpus() returns the maximum number of vcpus we had at some point (vs the real number of vcpus running). It was introduced with scoreboards, to ensure the user can know how many entries they contain, in a safe way. Most of the usage we had for this was to allocate structures and collect information per vcpu, especially at the end of execution (where some vcpus will have exited already in user-mode). So choosing to return the max is a valid abstraction. > | qemu_plugin_vcpu_for_each(id, vcpu_init); > > in qemu_plugin_install. > And indeed, qemu_plugin_vcpu_for_each(id, vcpu_init) will only iterate on active cpus. >>> >>> + break; >>> + } >>> +} >>> + >>> +static void plugin_exit(qemu_plugin_id_t id, void *p) >>> +{ >>> + g_autoptr(GString) report; >>> + report = g_string_new("VCPU, interrupts, exceptions, hostcalls\n"); >>> + int vcpu; >>> + >>> + for (vcpu = 0; vcpu < max_vcpus; vcpu++) { >>> >> vcpu < qemu_plugin_num_vcpus() > > Yes, max_vcpus was introduced as an optimization. If we can rely on all > VCPUs with id < qemu_plugin_num_vcpus() having been active at some point > this becomes unnecessary. > >>> + qemu_plugin_register_vcpu_discon_cb(id, QEMU_PLUGIN_DISCON_TRAPS, >>> + vcpu_discon); >>> + >>> >> The change from QEMU_PLUGIN_DISCON_TRAPS to QEMU_PLUGIN_DISCON_ALL should be included in this patch, instead of next one. > > Ah, thanks for pointing that out. I likely fumbled this at some point when rebasing. > > Regards, > Julian Ganz
diff --git a/contrib/plugins/meson.build b/contrib/plugins/meson.build index 63a32c2b4f..9a3015e1c1 100644 --- a/contrib/plugins/meson.build +++ b/contrib/plugins/meson.build @@ -1,5 +1,6 @@ contrib_plugins = ['bbv', 'cache', 'cflow', 'drcov', 'execlog', 'hotblocks', - 'hotpages', 'howvec', 'hwprofile', 'ips', 'stoptrigger'] + 'hotpages', 'howvec', 'hwprofile', 'ips', 'stoptrigger', + 'traps'] if host_os != 'windows' # lockstep uses socket.h contrib_plugins += 'lockstep' diff --git a/contrib/plugins/traps.c b/contrib/plugins/traps.c new file mode 100644 index 0000000000..ecd4beac5f --- /dev/null +++ b/contrib/plugins/traps.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024, Julian Ganz <neither@nut.email> + * + * Traps - count traps + * + * License: GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#include <stdio.h> + +#include <qemu-plugin.h> + +QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION; + +typedef struct { + uint64_t interrupts; + uint64_t exceptions; + uint64_t hostcalls; + bool active; +} TrapCounters; + +static struct qemu_plugin_scoreboard *traps; +static size_t max_vcpus; + +static void vcpu_init(qemu_plugin_id_t id, unsigned int vcpu_index) +{ + TrapCounters* rec = qemu_plugin_scoreboard_find(traps, vcpu_index); + rec->active = true; +} + +static void vcpu_discon(qemu_plugin_id_t id, unsigned int vcpu_index, + enum qemu_plugin_discon_type type, uint64_t from_pc, + uint64_t to_pc) +{ + TrapCounters* rec = qemu_plugin_scoreboard_find(traps, vcpu_index); + switch (type) { + case QEMU_PLUGIN_DISCON_INTERRUPT: + rec->interrupts++; + break; + case QEMU_PLUGIN_DISCON_EXCEPTION: + rec->exceptions++; + break; + case QEMU_PLUGIN_DISCON_HOSTCALL: + rec->hostcalls++; + break; + default: + /* unreachable */ + break; + } +} + +static void plugin_exit(qemu_plugin_id_t id, void *p) +{ + g_autoptr(GString) report; + report = g_string_new("VCPU, interrupts, exceptions, hostcalls\n"); + int vcpu; + + for (vcpu = 0; vcpu < max_vcpus; vcpu++) { + TrapCounters *rec = qemu_plugin_scoreboard_find(traps, vcpu); + if (rec->active) { + g_string_append_printf(report, + "% 4d, % 10"PRId64", % 10"PRId64", % 10" + PRId64"\n", + vcpu, + rec->interrupts, rec->exceptions, + rec->hostcalls); + } + } + + qemu_plugin_outs(report->str); + qemu_plugin_scoreboard_free(traps); +} + +QEMU_PLUGIN_EXPORT +int qemu_plugin_install(qemu_plugin_id_t id, const qemu_info_t *info, + int argc, char **argv) +{ + if (!info->system_emulation) { + fputs("trap plugin can only be used in system emulation mode.\n", + stderr); + return -1; + } + + max_vcpus = info->system.max_vcpus; + traps = qemu_plugin_scoreboard_new(sizeof(TrapCounters)); + qemu_plugin_register_vcpu_init_cb(id, vcpu_init); + qemu_plugin_vcpu_for_each(id, vcpu_init); + + qemu_plugin_register_vcpu_discon_cb(id, QEMU_PLUGIN_DISCON_TRAPS, + vcpu_discon); + + qemu_plugin_register_atexit_cb(id, plugin_exit, NULL); + + return 0; +}