diff mbox series

[RFC,v3,04/11] contrib/plugins: add plugin showcasing new dicontinuity related API

Message ID fa02142d349ceb6c95e80301a7f5c57ae5df6329.1733063076.git.neither@nut.email (mailing list archive)
State New
Headers show
Series tcg-plugins: add hooks for discontinuities | expand

Commit Message

Julian Ganz Dec. 2, 2024, 7:26 p.m. UTC
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

Comments

Pierrick Bouvier Dec. 4, 2024, 11:14 p.m. UTC | #1
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;
> +}
Julian Ganz Dec. 5, 2024, 1 p.m. UTC | #2
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
Pierrick Bouvier Dec. 5, 2024, 5:23 p.m. UTC | #3
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 mbox series

Patch

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;
+}