diff mbox

[v4,01/18] migration: dump vmstate info as a json file for static analysis

Message ID 4ab8ba6bf5de4943288c1ddc565179c807f263e3.1403079139.git.amit.shah@redhat.com
State New
Headers show

Commit Message

Amit Shah June 18, 2014, 8:13 a.m. UTC
This commit adds a new command, '-dump-vmstate', that takes a filename
as a parameter.  When executed, QEMU will dump the vmstate information
for the machine type it's invoked with to the file, and quit.

The JSON-format output can then be used to compare the vmstate info for
different QEMU versions, specifically to test whether live migration
would break due to changes in the vmstate data.

A Python script that compares the output of such JSON dumps is included
in the following commit.

Signed-off-by: Amit Shah <amit.shah@redhat.com>
---
 include/migration/vmstate.h |   2 +
 qemu-options.hx             |   9 +++
 savevm.c                    | 139 ++++++++++++++++++++++++++++++++++++++++++++
 vl.c                        |  13 +++++
 4 files changed, 163 insertions(+)

Comments

Juan Quintela June 18, 2014, 10:24 a.m. UTC | #1
Amit Shah <amit.shah@redhat.com> wrote:
> This commit adds a new command, '-dump-vmstate', that takes a filename
> as a parameter.  When executed, QEMU will dump the vmstate information
> for the machine type it's invoked with to the file, and quit.
>
> The JSON-format output can then be used to compare the vmstate info for
> different QEMU versions, specifically to test whether live migration
> would break due to changes in the vmstate data.
>
> A Python script that compares the output of such JSON dumps is included
> in the following commit.
>
> Signed-off-by: Amit Shah <amit.shah@redhat.com>



> +static void dump_vmstate_vmsd(FILE *out_file,
> +                              const VMStateDescription *vmsd, int indent,
> +                              bool is_subsection)
> +{
> +    if (is_subsection) {
> +        fprintf(out_file, "%*s{\n", indent, "");
> +    } else {
> +        fprintf(out_file, "%*s\"%s\": {\n", indent, "", "Description");
> +    }
> +    indent += 2;
> +    fprintf(out_file, "%*s\"name\": \"%s\",\n", indent, "", vmsd->name);
> +    fprintf(out_file, "%*s\"version_id\": %d,\n", indent, "",
> +            vmsd->version_id);
> +    fprintf(out_file, "%*s\"minimum_version_id\": %d", indent, "",
> +            vmsd->minimum_version_id);
> +    if (vmsd->fields != NULL) {
> +        const VMStateField *field = vmsd->fields;
> +        bool first;
> +
> +        fprintf(out_file, ",\n%*s\"Fields\": [\n", indent, "");

Remove last "\n" (*)


> +        first = true;

first can go now

> +        while (field->name != NULL) {
> +            if (field->flags & VMS_MUST_EXIST) {
> +                /* Ignore VMSTATE_VALIDATE bits; these don't get migrated */
> +                field++;
> +                continue;
> +            }
> +            if (!first) {
> +                fprintf(out_file, ",\n");

You can print always \n now, right?

Same for the other places?  Or I am missing something.

I will even go that itwould be better to just left the \n on the (*),
and just add this \n at the end of writing a subsection.


> +    fprintf(out_file, "\n%*s}", indent - 2, "");

And you remove it from here.

<full disclosure>
Yes, I have always hated pretty-printers
</full-disclosure>

Rest of this, I fully agree.

Later, Juan.
Amit Shah June 18, 2014, 10:36 a.m. UTC | #2
On (Wed) 18 Jun 2014 [12:24:25], Juan Quintela wrote:
> Amit Shah <amit.shah@redhat.com> wrote:
> > This commit adds a new command, '-dump-vmstate', that takes a filename
> > as a parameter.  When executed, QEMU will dump the vmstate information
> > for the machine type it's invoked with to the file, and quit.
> >
> > The JSON-format output can then be used to compare the vmstate info for
> > different QEMU versions, specifically to test whether live migration
> > would break due to changes in the vmstate data.
> >
> > A Python script that compares the output of such JSON dumps is included
> > in the following commit.
> >
> > Signed-off-by: Amit Shah <amit.shah@redhat.com>
> 
> 
> 
> > +static void dump_vmstate_vmsd(FILE *out_file,
> > +                              const VMStateDescription *vmsd, int indent,
> > +                              bool is_subsection)
> > +{
> > +    if (is_subsection) {
> > +        fprintf(out_file, "%*s{\n", indent, "");
> > +    } else {
> > +        fprintf(out_file, "%*s\"%s\": {\n", indent, "", "Description");
> > +    }
> > +    indent += 2;
> > +    fprintf(out_file, "%*s\"name\": \"%s\",\n", indent, "", vmsd->name);
> > +    fprintf(out_file, "%*s\"version_id\": %d,\n", indent, "",
> > +            vmsd->version_id);
> > +    fprintf(out_file, "%*s\"minimum_version_id\": %d", indent, "",
> > +            vmsd->minimum_version_id);
> > +    if (vmsd->fields != NULL) {
> > +        const VMStateField *field = vmsd->fields;
> > +        bool first;
> > +
> > +        fprintf(out_file, ",\n%*s\"Fields\": [\n", indent, "");
> 
> Remove last "\n" (*)
> 
> 
> > +        first = true;
> 
> first can go now
> 
> > +        while (field->name != NULL) {
> > +            if (field->flags & VMS_MUST_EXIST) {
> > +                /* Ignore VMSTATE_VALIDATE bits; these don't get migrated */
> > +                field++;
> > +                continue;
> > +            }
> > +            if (!first) {
> > +                fprintf(out_file, ",\n");
> 
> You can print always \n now, right?

There's also a , there...

This sequence was added recently (v2 onwards) for the ignoring of the
VMS_MUST_EXIST stuff.

> Same for the other places?  Or I am missing something.
> 
> I will even go that itwould be better to just left the \n on the (*),
> and just add this \n at the end of writing a subsection.
> 
> 
> > +    fprintf(out_file, "\n%*s}", indent - 2, "");
> 
> And you remove it from here.

I tried several things with the \n; the current setting is the best I
found.

Of course, this is just pretty-printing, so I don't actually remember
all the details but I can look it up my git tree...

> <full disclosure>
> Yes, I have always hated pretty-printers
> </full-disclosure>

Oh, me too!

> Rest of this, I fully agree.
> 
> Later, Juan.

		Amit
Juan Quintela June 18, 2014, 10:56 a.m. UTC | #3
Amit Shah <amit.shah@redhat.com> wrote:
>> 
>> You can print always \n now, right?
>
> There's also a , there...
>
> This sequence was added recently (v2 onwards) for the ignoring of the
> VMS_MUST_EXIST stuff.

I knew it needed to be some reason for the ugliness :-()

>> Same for the other places?  Or I am missing something.
>> 
>> I will even go that itwould be better to just left the \n on the (*),
>> and just add this \n at the end of writing a subsection.
>> 
>> 
>> > +    fprintf(out_file, "\n%*s}", indent - 2, "");
>> 
>> And you remove it from here.
>
> I tried several things with the \n; the current setting is the best I
> found.
>
> Of course, this is just pretty-printing, so I don't actually remember
> all the details but I can look it up my git tree...

ok, I retire the comment, and anyways, it is trivial to fix
incrementally if you/anybody came with a clever idea.

Reviewed-by: Juan Quintela <quintela@redhat.com>
diff mbox

Patch

diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index 7e45048..9829c0e 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -778,4 +778,6 @@  void vmstate_register_ram(struct MemoryRegion *memory, DeviceState *dev);
 void vmstate_unregister_ram(struct MemoryRegion *memory, DeviceState *dev);
 void vmstate_register_ram_global(struct MemoryRegion *memory);
 
+void dump_vmstate_json_to_file(FILE *out_fp);
+
 #endif
diff --git a/qemu-options.hx b/qemu-options.hx
index d0714c4..48d493b 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -3221,6 +3221,15 @@  STEXI
 prepend a timestamp to each log message.(default:on)
 ETEXI
 
+DEF("dump-vmstate", HAS_ARG, QEMU_OPTION_dump_vmstate,
+    "-dump-vmstate <file>\n" "", QEMU_ARCH_ALL)
+STEXI
+@item -dump-vmstate @var{file}
+@findex -dump-vmstate
+Dump json-encoded vmstate information for current machine type to file
+in @var{file}
+ETEXI
+
 HXCOMM This is the last statement. Insert new options before this line!
 STEXI
 @end table
diff --git a/savevm.c b/savevm.c
index da8aa24..0c8ad45 100644
--- a/savevm.c
+++ b/savevm.c
@@ -24,6 +24,7 @@ 
 
 #include "config-host.h"
 #include "qemu-common.h"
+#include "hw/boards.h"
 #include "hw/hw.h"
 #include "hw/qdev.h"
 #include "net/net.h"
@@ -241,6 +242,144 @@  static QTAILQ_HEAD(savevm_handlers, SaveStateEntry) savevm_handlers =
     QTAILQ_HEAD_INITIALIZER(savevm_handlers);
 static int global_section_id;
 
+static void dump_vmstate_vmsd(FILE *out_file,
+                              const VMStateDescription *vmsd, int indent,
+                              bool is_subsection);
+
+static void dump_vmstate_vmsf(FILE *out_file, const VMStateField *field,
+                              int indent)
+{
+    fprintf(out_file, "%*s{\n", indent, "");
+    indent += 2;
+    fprintf(out_file, "%*s\"field\": \"%s\",\n", indent, "", field->name);
+    fprintf(out_file, "%*s\"version_id\": %d,\n", indent, "",
+            field->version_id);
+    fprintf(out_file, "%*s\"field_exists\": %s,\n", indent, "",
+            field->field_exists ? "true" : "false");
+    fprintf(out_file, "%*s\"size\": %zu", indent, "", field->size);
+    if (field->vmsd != NULL) {
+        fprintf(out_file, ",\n");
+        dump_vmstate_vmsd(out_file, field->vmsd, indent, false);
+    }
+    fprintf(out_file, "\n%*s}", indent - 2, "");
+}
+
+static void dump_vmstate_vmss(FILE *out_file,
+                              const VMStateSubsection *subsection,
+                              int indent)
+{
+    if (subsection->vmsd != NULL) {
+        dump_vmstate_vmsd(out_file, subsection->vmsd, indent, true);
+    }
+}
+
+static void dump_vmstate_vmsd(FILE *out_file,
+                              const VMStateDescription *vmsd, int indent,
+                              bool is_subsection)
+{
+    if (is_subsection) {
+        fprintf(out_file, "%*s{\n", indent, "");
+    } else {
+        fprintf(out_file, "%*s\"%s\": {\n", indent, "", "Description");
+    }
+    indent += 2;
+    fprintf(out_file, "%*s\"name\": \"%s\",\n", indent, "", vmsd->name);
+    fprintf(out_file, "%*s\"version_id\": %d,\n", indent, "",
+            vmsd->version_id);
+    fprintf(out_file, "%*s\"minimum_version_id\": %d", indent, "",
+            vmsd->minimum_version_id);
+    if (vmsd->fields != NULL) {
+        const VMStateField *field = vmsd->fields;
+        bool first;
+
+        fprintf(out_file, ",\n%*s\"Fields\": [\n", indent, "");
+        first = true;
+        while (field->name != NULL) {
+            if (field->flags & VMS_MUST_EXIST) {
+                /* Ignore VMSTATE_VALIDATE bits; these don't get migrated */
+                field++;
+                continue;
+            }
+            if (!first) {
+                fprintf(out_file, ",\n");
+            }
+            dump_vmstate_vmsf(out_file, field, indent + 2);
+            field++;
+            first = false;
+        }
+        fprintf(out_file, "\n%*s]", indent, "");
+    }
+    if (vmsd->subsections != NULL) {
+        const VMStateSubsection *subsection = vmsd->subsections;
+        bool first;
+
+        fprintf(out_file, ",\n%*s\"Subsections\": [\n", indent, "");
+        first = true;
+        while (subsection->vmsd != NULL) {
+            if (!first) {
+                fprintf(out_file, ",\n");
+            }
+            dump_vmstate_vmss(out_file, subsection, indent + 2);
+            subsection++;
+            first = false;
+        }
+        fprintf(out_file, "\n%*s]", indent, "");
+    }
+    fprintf(out_file, "\n%*s}", indent - 2, "");
+}
+
+static void dump_machine_type(FILE *out_file)
+{
+    MachineClass *mc;
+
+    mc = MACHINE_GET_CLASS(current_machine);
+
+    fprintf(out_file, "  \"vmschkmachine\": {\n");
+    fprintf(out_file, "    \"Name\": \"%s\"\n", mc->name);
+    fprintf(out_file, "  },\n");
+}
+
+void dump_vmstate_json_to_file(FILE *out_file)
+{
+    GSList *list, *elt;
+    bool first;
+
+    fprintf(out_file, "{\n");
+    dump_machine_type(out_file);
+
+    first = true;
+    list = object_class_get_list(TYPE_DEVICE, true);
+    for (elt = list; elt; elt = elt->next) {
+        DeviceClass *dc = OBJECT_CLASS_CHECK(DeviceClass, elt->data,
+                                             TYPE_DEVICE);
+        const char *name;
+        int indent = 2;
+
+        if (!dc->vmsd) {
+            continue;
+        }
+
+        if (!first) {
+            fprintf(out_file, ",\n");
+        }
+        name = object_class_get_name(OBJECT_CLASS(dc));
+        fprintf(out_file, "%*s\"%s\": {\n", indent, "", name);
+        indent += 2;
+        fprintf(out_file, "%*s\"Name\": \"%s\",\n", indent, "", name);
+        fprintf(out_file, "%*s\"version_id\": %d,\n", indent, "",
+                dc->vmsd->version_id);
+        fprintf(out_file, "%*s\"minimum_version_id\": %d,\n", indent, "",
+                dc->vmsd->minimum_version_id);
+
+        dump_vmstate_vmsd(out_file, dc->vmsd, indent, false);
+
+        fprintf(out_file, "\n%*s}", indent - 2, "");
+        first = false;
+    }
+    fprintf(out_file, "\n}\n");
+    fclose(out_file);
+}
+
 static int calculate_new_instance_id(const char *idstr)
 {
     SaveStateEntry *se;
diff --git a/vl.c b/vl.c
index be69c7f..860541f 100644
--- a/vl.c
+++ b/vl.c
@@ -2991,6 +2991,7 @@  int main(int argc, char **argv, char **envp)
     const char *trace_file = NULL;
     const ram_addr_t default_ram_size = (ram_addr_t)DEFAULT_RAM_SIZE *
                                         1024 * 1024;
+    FILE *vmstate_dump_file = NULL;
 
     atexit(qemu_run_exit_notifiers);
     error_set_progname(argv[0]);
@@ -3957,6 +3958,13 @@  int main(int argc, char **argv, char **envp)
                 }
                 configure_msg(opts);
                 break;
+            case QEMU_OPTION_dump_vmstate:
+                vmstate_dump_file = fopen(optarg, "w");
+                if (vmstate_dump_file == NULL) {
+                    fprintf(stderr, "open %s: %s\n", optarg, strerror(errno));
+                    exit(1);
+                }
+                break;
             default:
                 os_parse_cmd_args(popt->index, optarg);
             }
@@ -4542,6 +4550,11 @@  int main(int argc, char **argv, char **envp)
     }
 
     qdev_prop_check_global();
+    if (vmstate_dump_file) {
+        /* dump and exit */
+        dump_vmstate_json_to_file(vmstate_dump_file);
+        return 0;
+    }
 
     if (incoming) {
         Error *local_err = NULL;