diff mbox

[RFC,v3,3/6] VMstate test: basic VMState testing mechanism

Message ID 1407565595-18861-4-git-send-email-sanidhya.iiith@gmail.com
State New
Headers show

Commit Message

Sanidhya Kashyap Aug. 9, 2014, 6:26 a.m. UTC
This patch implements the basic way of testing the VMStates' information
whether it is correct or not while saving and loading the states. The qmp
interface - test-vmstates can take three parameters as an input to test
the device states. Now, one can check for any of the devices that have been
registered with the SaveVMHandlers aka the migration protocol. Similarly,
the hmp interface (test_vmstates) has only two input parameters - iterations and
period.

I have removed the following from the patch on previous comments:
* replaed DPRINTF with trace_##name.
* removed the noqdev and completely removed the support for resetting of devices
based on qemu_system_reset()

As Juan pointed out that there might be a memory leak as I did not close the
QEMUFile pointer. But, it is not required as that pointer is directly referenced
by the QEMUBuffer that has been implemented by David. So, IMHO the case pointed
out by Juan does not exist.


Signed-off-by: Sanidhya Kashyap <sanidhya.iiith@gmail.com>
---
 hmp-commands.hx  |  16 ++++
 hmp.c            |  17 ++++
 hmp.h            |   1 +
 qapi-schema.json |  22 ++++++
 qmp-commands.hx  |  33 ++++++++
 savevm.c         | 233 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 trace-events     |   2 +
 7 files changed, 324 insertions(+)

Comments

Eric Blake Aug. 11, 2014, 4:32 p.m. UTC | #1
On 08/09/2014 12:26 AM, Sanidhya Kashyap wrote:
> This patch implements the basic way of testing the VMStates' information
> whether it is correct or not while saving and loading the states. The qmp
> interface - test-vmstates can take three parameters as an input to test
> the device states. Now, one can check for any of the devices that have been
> registered with the SaveVMHandlers aka the migration protocol. Similarly,
> the hmp interface (test_vmstates) has only two input parameters - iterations and
> period.
> 

This paragraph:

vvvvvvv
> I have removed the following from the patch on previous comments:
> * replaed DPRINTF with trace_##name.
> * removed the noqdev and completely removed the support for resetting of devices
> based on qemu_system_reset()
^^^^^^^

should not be part of the commit message proper, but...

> 
> As Juan pointed out that there might be a memory leak as I did not close the
> QEMUFile pointer. But, it is not required as that pointer is directly referenced
> by the QEMUBuffer that has been implemented by David. So, IMHO the case pointed
> out by Juan does not exist.
> 
> 
> Signed-off-by: Sanidhya Kashyap <sanidhya.iiith@gmail.com>
> ---

...listed here, as a changelog to guide reviewers.  Remember, anything
before the --- should stand alone as what you would read in qemu.git,
without regards to how many revisions the patch went through; and
anything after the --- is useful for reviewers but intentionally
stripped by the maintainer when using 'git am' as fluff that doesn't
help explain the commit in isolation.

>  hmp-commands.hx  |  16 ++++
>  hmp.c            |  17 ++++
>  hmp.h            |   1 +
>  qapi-schema.json |  22 ++++++
>  qmp-commands.hx  |  33 ++++++++
>  savevm.c         | 233 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  trace-events     |   2 +
>  7 files changed, 324 insertions(+)
> 

> +++ b/qapi-schema.json
> @@ -3506,3 +3506,25 @@
>  ##
>  { 'command': 'query-devices',
>    'returns': [ 'QemuDevice' ] }
> +
> +##
> +# @test-vmstates
> +#
> +# tests the vmstates' value by dumping and loading in memory
> +#
> +# @iterations: (optional) The total iterations for vmstate testing.

For consistency, and in case we ever start generating documentation from
the .json file,
s/(optional)/#optional/

> +# The min and max defind value is 10 and 100 respectively.

s/defind value is/defined values are/

> +#
> +# @period: (optional) sleep interval between iteration (in milliseconds).

s/(optional)/#optional/

> +# The default interval is 100 milliseconds with min and max being
> +# 1 and 10000 respectively.
> +#
> +# @devices: (optional) helps in resetting particular device(s) that
> +# have been registered with SaveStateEntry.
> +#
> +# Since 2.2
> +##
> +{ 'command': 'test-vmstates',
> +  'data': {'*iterations':  'int',
> +           '*period':      'int',
> +           '*devices':   [ 'QemuDevice' ] } }

> +Example:
> +
> +-> { "execute": "test-vmstates",
> +     "arguments": {
> +        "iterations": 10,
> +        "period": 100, } }

That is not valid JSON.  You cannot have a trailing , inside {}.  Also,
it might be worth an example of the proper use of the optional "devices"
parameter.


> +
> +static inline bool test_vmstates_check_device_name(VMStateLogState *v,

The compiler is good enough about inlining static functions that you
seldom need to use 'inline'.  That, and 'static inline' has changed
semantics over the years of gcc, so you really want to avoid it unless
you know what you are doing.


> +static void test_vmstates_cb(void *opaque)
> +{
> +    VMStateLogState *v = opaque;
> +    int saved_vm_running = runstate_is_running();
> +    const QEMUSizedBuffer *qsb;

> +
> +        qsb = qemu_buf_get(f);
> +
> +        /* clearing the states of the guest */
> +        test_vmstates_reset_devices(v);
> +
> +        start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
> +        f = qemu_bufopen("r", (QEMUSizedBuffer *)qsb);

Ewww.  Why are you casting away const?  Make qsb the correct type to
begin with.
diff mbox

Patch

diff --git a/hmp-commands.hx b/hmp-commands.hx
index a221459..e16e1ac 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -1790,6 +1790,22 @@  STEXI
 show available trace events and their state
 ETEXI
 
+    {
+        .name       = "test_vmstates",
+        .args_type  = "iterations:i?,period:i?",
+        .params     = "iterations period",
+        .help       = "test the vmstates by dumping and loading form memory\n\t\t\t"
+                      "iterations: (optional) number of times, the vmstates will be tested\n\t\t\t"
+                      "period: (optional) sleep interval in milliseconds between each iteration",
+        .mhandler.cmd = hmp_test_vmstates,
+    },
+STEXI
+@item test_vmstates
+@findex test_vmstates
+dumps and reads the device state's data from the memory for testing purpose
+ETEXI
+
+
 STEXI
 @end table
 ETEXI
diff --git a/hmp.c b/hmp.c
index 6e1802a..ca7664e 100644
--- a/hmp.c
+++ b/hmp.c
@@ -1737,3 +1737,20 @@  void hmp_info_devices(Monitor *mon, const QDict *qdict)
 
     qapi_free_QemuDeviceList(device_list);
 }
+void hmp_test_vmstates(Monitor *mon, const QDict *qdict)
+{
+    int has_iterations = qdict_haskey(qdict, "iterations");
+    int64_t iterations = qdict_get_try_int(qdict, "iterations", 10);
+    int has_period = qdict_haskey(qdict, "period");
+    int64_t period = qdict_get_try_int(qdict, "period", 100);
+
+    Error *err = NULL;
+
+    qmp_test_vmstates(has_iterations, iterations, has_period, period,
+                      false, NULL, &err);
+
+    if (err) {
+        monitor_printf(mon, "test-vmstates: %s\n", error_get_pretty(err));
+        error_free(err);
+    }
+}
diff --git a/hmp.h b/hmp.h
index 9d6b577..56fb865 100644
--- a/hmp.h
+++ b/hmp.h
@@ -95,6 +95,7 @@  void hmp_cpu_add(Monitor *mon, const QDict *qdict);
 void hmp_object_add(Monitor *mon, const QDict *qdict);
 void hmp_object_del(Monitor *mon, const QDict *qdict);
 void hmp_info_memdev(Monitor *mon, const QDict *qdict);
+void hmp_test_vmstates(Monitor *mon, const QDict *qdict);
 void object_add_completion(ReadLineState *rs, int nb_args, const char *str);
 void object_del_completion(ReadLineState *rs, int nb_args, const char *str);
 void device_add_completion(ReadLineState *rs, int nb_args, const char *str);
diff --git a/qapi-schema.json b/qapi-schema.json
index 74d26fe..4c810d7 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3506,3 +3506,25 @@ 
 ##
 { 'command': 'query-devices',
   'returns': [ 'QemuDevice' ] }
+
+##
+# @test-vmstates
+#
+# tests the vmstates' value by dumping and loading in memory
+#
+# @iterations: (optional) The total iterations for vmstate testing.
+# The min and max defind value is 10 and 100 respectively.
+#
+# @period: (optional) sleep interval between iteration (in milliseconds).
+# The default interval is 100 milliseconds with min and max being
+# 1 and 10000 respectively.
+#
+# @devices: (optional) helps in resetting particular device(s) that
+# have been registered with SaveStateEntry.
+#
+# Since 2.2
+##
+{ 'command': 'test-vmstates',
+  'data': {'*iterations':  'int',
+           '*period':      'int',
+           '*devices':   [ 'QemuDevice' ] } }
diff --git a/qmp-commands.hx b/qmp-commands.hx
index e489197..ae2bdca 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -3798,3 +3798,36 @@  Example (1):
      ]
    }
 EQMP
+
+    {
+        .name       = "test-vmstates",
+        .args_type  = "iterations:i?,period:i?,devices:O?",
+        .mhandler.cmd_new = qmp_marshal_input_test_vmstates,
+    },
+
+SQMP
+test-vmstates
+-------------
+
+Tests the vmstates' entry by dumping and loading in/from memory
+
+Arguments:
+
+- "iterations": (optional) The total iterations for vmstate testing.
+                 The min and max defined value is 10 and 100 respectively.
+
+- "period": (optional) sleep interval between iteration (in milliseconds).
+            The default interval is 100 milliseconds with min and max being
+            1 and 10000 respectively.
+
+- "devices": (optional) helps in resetting particular device(s)
+             that have been registered with SaveStateEntry.
+
+Example:
+
+-> { "execute": "test-vmstates",
+     "arguments": {
+        "iterations": 10,
+        "period": 100, } }
+<- { "return": {} }
+EQMP
diff --git a/savevm.c b/savevm.c
index 764ca71..cd67c5d 100644
--- a/savevm.c
+++ b/savevm.c
@@ -1182,6 +1182,239 @@  QemuDeviceList *qmp_query_devices(Error **errp)
     return device_list;
 }
 
+/*
+ * VMState testing
+ */
+
+#define TEST_VMSTATE_MIN_TIMES 10
+#define TEST_VMSTATE_MAX_TIMES 1000
+
+#define TEST_VMSTATE_MIN_INTERVAL_MS 1
+#define TEST_VMSTATE_DEFAULT_INTERVAL_MS 100
+#define TEST_VMSTATE_MAX_INTERVAL_MS 10000
+
+typedef struct VMStateLogState VMStateLogState;
+typedef struct DeviceInfoEntry DeviceInfoEntry;
+
+struct DeviceInfoEntry {
+    QemuDevice *device;
+    DeviceState *dev;
+    QTAILQ_ENTRY(DeviceInfoEntry) entry;
+};
+
+struct VMStateLogState {
+    int64_t current_iteration;
+    int64_t iterations;
+    int64_t period;
+    bool active_state;
+    QEMUTimer *timer;
+    QTAILQ_HEAD(device_list, DeviceInfoEntry) device_list;
+};
+
+static VMStateLogState *test_vmstates_get_current_state(void)
+{
+    static VMStateLogState current_state = {
+        .active_state = false,
+    };
+
+    return &current_state;
+}
+
+static inline void test_vmstates_clear_device_list(VMStateLogState *v)
+{
+    DeviceInfoEntry *de, *new_de;
+    QTAILQ_FOREACH_SAFE(de, &v->device_list, entry, new_de) {
+        QTAILQ_REMOVE(&v->device_list, de, entry);
+    }
+}
+
+static void test_vmstates_reset_devices(VMStateLogState *v)
+{
+    SaveStateEntry *se;
+    DeviceInfoEntry *de;
+
+    if (QTAILQ_EMPTY(&v->device_list)) {
+        QTAILQ_FOREACH(se, &savevm_handlers, entry) {
+            if (se->dev) {
+                trace_test_vmstates_reset_devices(se->vmsd->name);
+                device_reset(se->dev);
+            }
+        }
+    } else {
+        QTAILQ_FOREACH(de, &v->device_list, entry) {
+            trace_test_vmstates_reset_devices(de->device->device_name);
+            device_reset(de->dev);
+        }
+    }
+}
+
+static inline bool test_vmstates_check_device_name(VMStateLogState *v,
+                                                   QemuDeviceList *devices,
+                                                   Error **errp)
+{
+    SaveStateEntry *se;
+    bool device_present;
+    QTAILQ_INIT(&v->device_list);
+
+    /* now, checking against each one */
+    while (devices->next) {
+        DeviceInfoEntry *new_de;
+        device_present = false;
+        QTAILQ_FOREACH(se, &savevm_handlers, entry) {
+            if (!strcmp(se->vmsd->name, devices->value->device_name)) {
+                device_present = true;
+                break;
+            }
+        }
+        if (!device_present) {
+            test_vmstates_clear_device_list(v);
+            error_setg(errp, "Incorrect device name - %s\n",
+                       devices->value->device_name);
+            return false;
+        } else {
+            new_de = g_malloc0(sizeof(DeviceInfoEntry));
+            new_de->device = devices->value;
+            new_de->dev = se->dev;
+            QTAILQ_INSERT_TAIL(&v->device_list, new_de, entry);
+        }
+    }
+    return true;
+}
+
+static void test_vmstates_cb(void *opaque)
+{
+    VMStateLogState *v = opaque;
+    int saved_vm_running = runstate_is_running();
+    const QEMUSizedBuffer *qsb;
+    QEMUFile *f;
+    int ret;
+    int64_t save_vmstates_duration, load_vmstates_duration;
+    int64_t start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
+
+    /* executing the steps for a single time with the help of timer */
+    if (++(v->current_iteration) <= v->iterations) {
+        saved_vm_running = runstate_is_running();
+
+        /* stopping the VM before dumping the vmstates */
+        vm_stop(RUN_STATE_SAVE_VM);
+
+        f = qemu_bufopen("w", NULL);
+        if (!f) {
+            goto testing_end;
+        }
+
+        cpu_synchronize_all_states();
+
+        /* saving the vmsates to memory buffer */
+        ret = qemu_save_device_state(f);
+        if (ret < 0) {
+            goto testing_end;
+        }
+        save_vmstates_duration = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) -
+                                 start_time;
+        trace_test_vmstates_cb(v->current_iteration, save_vmstates_duration,
+                               "save time (ms)");
+
+        qsb = qemu_buf_get(f);
+
+        /* clearing the states of the guest */
+        test_vmstates_reset_devices(v);
+
+        start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
+        f = qemu_bufopen("r", (QEMUSizedBuffer *)qsb);
+        if (!f) {
+            goto testing_end;
+        }
+
+        /* loading the device states from the saved buffer */
+        ret = qemu_loadvm_state(f);
+        qemu_fclose(f);
+        if (ret < 0) {
+            goto testing_end;
+        }
+        load_vmstates_duration = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) -
+                                 start_time;
+        trace_test_vmstates_cb(v->current_iteration, load_vmstates_duration,
+                               "load time (ms)");
+
+        if (saved_vm_running) {
+            vm_start();
+        }
+        timer_mod(v->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME) +
+                                              v->period);
+        return;
+    }
+
+ testing_end:
+    if (saved_vm_running) {
+        vm_start();
+    }
+    timer_del(v->timer);
+    timer_free(v->timer);
+    test_vmstates_clear_device_list(v);
+    v->active_state = false;
+    return;
+}
+
+void qmp_test_vmstates(bool has_iterations, int64_t iterations,
+                       bool has_period, int64_t period, bool has_devices,
+                       QemuDeviceList *devices, Error **errp)
+{
+    VMStateLogState *v = test_vmstates_get_current_state();
+    Error *local_err;
+
+    if (v->active_state) {
+        error_setg(errp, "VMState testing already in progress\n");
+        return;
+    }
+
+    v->active_state = true;
+
+    /* checking the value of total iterations to be in the defined range */
+    if (!has_iterations) {
+        v->iterations = TEST_VMSTATE_MIN_TIMES;
+    } else if (iterations >= TEST_VMSTATE_MIN_TIMES &&
+               iterations <= TEST_VMSTATE_MAX_TIMES) {
+        v->iterations = iterations;
+    } else {
+        error_setg(errp, "iterations value must be in the range [%d, %d]\n",
+                   TEST_VMSTATE_MIN_TIMES, TEST_VMSTATE_MAX_TIMES);
+        v->active_state = false;
+        return;
+    }
+
+    /* checking for the value of period to be in the defined range */
+    if (!has_period) {
+        v->period = TEST_VMSTATE_DEFAULT_INTERVAL_MS;
+    } else if (period >= TEST_VMSTATE_MIN_INTERVAL_MS &&
+               period <= TEST_VMSTATE_MAX_INTERVAL_MS) {
+        v->period = period;
+    } else {
+        error_setg(errp, "sleep interval (period) value must be "
+                   "in the defined range [%d, %d](ms)\n",
+                   TEST_VMSTATE_MIN_INTERVAL_MS, TEST_VMSTATE_MAX_INTERVAL_MS);
+        v->active_state = false;
+        return;
+    }
+
+    if (!has_devices) {
+        QTAILQ_INIT(&v->device_list);
+    } else if (!test_vmstates_check_device_name(v, devices, &local_err)) {
+        if (local_err) {
+            error_propagate(errp, local_err);
+        }
+        return;
+    }
+
+    v->current_iteration = 0;
+    v->timer = timer_new_ms(QEMU_CLOCK_REALTIME, test_vmstates_cb, v);
+    timer_mod(v->timer, qemu_clock_get_ms(QEMU_CLOCK_REALTIME));
+}
+
+/*
+ * ----------------------------------------------------------------------------
+ */
+
 void qmp_xen_save_devices_state(const char *filename, Error **errp)
 {
     QEMUFile *f;
diff --git a/trace-events b/trace-events
index 11a17a8..bb00661 100644
--- a/trace-events
+++ b/trace-events
@@ -1086,6 +1086,8 @@  vmstate_save(const char *idstr, const char *vmsd_name) "%s, %s"
 vmstate_load(const char *idstr, const char *vmsd_name) "%s, %s"
 vmstate_load_field_error(const char *field, int ret) "field \"%s\" load failed, ret = %d"
 qemu_announce_self_iter(const char *mac) "%s"
+test_vmstates_reset_devices(const char *name) "resetting device: \"%s\""
+test_vmstates_cb(int64_t iteration, int64_t time, const char *info) "iteration: %ld, %s: %ld"
 
 # qemu-file.c
 qemu_file_fclose(void) ""