xref: /qemu/tests/plugin/syscall.c (revision b2a3cbb8)
1 /*
2  * Copyright (C) 2020, Matthias Weckbecker <matthias@weckbecker.name>
3  *
4  * License: GNU GPL, version 2 or later.
5  *   See the COPYING file in the top-level directory.
6  */
7 #include <inttypes.h>
8 #include <assert.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <unistd.h>
12 #include <stdio.h>
13 #include <glib.h>
14 
15 #include <qemu-plugin.h>
16 
17 QEMU_PLUGIN_EXPORT int qemu_plugin_version = QEMU_PLUGIN_VERSION;
18 
19 typedef struct {
20     int64_t num;
21     int64_t calls;
22     int64_t errors;
23 } SyscallStats;
24 
25 static GMutex lock;
26 static GHashTable *statistics;
27 
28 static SyscallStats *get_or_create_entry(int64_t num)
29 {
30     SyscallStats *entry =
31         (SyscallStats *) g_hash_table_lookup(statistics, GINT_TO_POINTER(num));
32 
33     if (!entry) {
34         entry = g_new0(SyscallStats, 1);
35         entry->num = num;
36         g_hash_table_insert(statistics, GINT_TO_POINTER(num), (gpointer) entry);
37     }
38 
39     return entry;
40 }
41 
42 static void vcpu_syscall(qemu_plugin_id_t id, unsigned int vcpu_index,
43                          int64_t num, uint64_t a1, uint64_t a2,
44                          uint64_t a3, uint64_t a4, uint64_t a5,
45                          uint64_t a6, uint64_t a7, uint64_t a8)
46 {
47     if (statistics) {
48         SyscallStats *entry;
49         g_mutex_lock(&lock);
50         entry = get_or_create_entry(num);
51         entry->calls++;
52         g_mutex_unlock(&lock);
53     } else {
54         g_autofree gchar *out = g_strdup_printf("syscall #%" PRIi64 "\n", num);
55         qemu_plugin_outs(out);
56     }
57 }
58 
59 static void vcpu_syscall_ret(qemu_plugin_id_t id, unsigned int vcpu_idx,
60                              int64_t num, int64_t ret)
61 {
62     if (statistics) {
63         SyscallStats *entry;
64 
65         g_mutex_lock(&lock);
66         /* Should always return an existent entry. */
67         entry = get_or_create_entry(num);
68         if (ret < 0) {
69             entry->errors++;
70         }
71         g_mutex_unlock(&lock);
72     } else {
73         g_autofree gchar *out = g_strdup_printf(
74              "syscall #%" PRIi64 " returned -> %" PRIi64 "\n", num, ret);
75         qemu_plugin_outs(out);
76     }
77 }
78 
79 static void print_entry(gpointer val, gpointer user_data)
80 {
81     SyscallStats *entry = (SyscallStats *) val;
82     int64_t syscall_num = entry->num;
83     g_autofree gchar *out = g_strdup_printf(
84         "%-13" PRIi64 "%-6" PRIi64 " %" PRIi64 "\n",
85         syscall_num, entry->calls, entry->errors);
86     qemu_plugin_outs(out);
87 }
88 
89 static gint comp_func(gconstpointer ea, gconstpointer eb)
90 {
91     SyscallStats *ent_a = (SyscallStats *) ea;
92     SyscallStats *ent_b = (SyscallStats *) eb;
93 
94     return ent_a->calls > ent_b->calls ? -1 : 1;
95 }
96 
97 /* ************************************************************************* */
98 static void plugin_exit(qemu_plugin_id_t id, void *p)
99 {
100     if (!statistics) {
101         return;
102     }
103 
104     g_mutex_lock(&lock);
105     GList *entries = g_hash_table_get_values(statistics);
106     entries = g_list_sort(entries, comp_func);
107     qemu_plugin_outs("syscall no.  calls  errors\n");
108 
109     g_list_foreach(entries, print_entry, NULL);
110 
111     g_list_free(entries);
112     g_hash_table_destroy(statistics);
113     g_mutex_unlock(&lock);
114 }
115 
116 QEMU_PLUGIN_EXPORT int qemu_plugin_install(qemu_plugin_id_t id,
117                                            const qemu_info_t *info,
118                                            int argc, char **argv)
119 {
120     bool do_print = false;
121 
122     for (int i = 0; i < argc; i++) {
123         char *opt = argv[i];
124         g_autofree char **tokens = g_strsplit(opt, "=", 2);
125 
126         if (g_strcmp0(tokens[0], "print") == 0) {
127             if (!qemu_plugin_bool_parse(tokens[0], tokens[1], &do_print)) {
128                 fprintf(stderr, "boolean argument parsing failed: %s\n", opt);
129             }
130         } else {
131             fprintf(stderr, "unsupported argument: %s\n", argv[i]);
132             return -1;
133         }
134     }
135 
136     if (!do_print) {
137         statistics = g_hash_table_new_full(NULL, g_direct_equal, NULL, g_free);
138     }
139 
140     qemu_plugin_register_vcpu_syscall_cb(id, vcpu_syscall);
141     qemu_plugin_register_vcpu_syscall_ret_cb(id, vcpu_syscall_ret);
142     qemu_plugin_register_atexit_cb(id, plugin_exit, NULL);
143     return 0;
144 }
145