qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 02/18] vmstate-static-checker: script to validate vm


From: Amit Shah
Subject: [Qemu-devel] [PATCH 02/18] vmstate-static-checker: script to validate vmstate changes
Date: Mon, 12 May 2014 16:46:02 +0530

This script compares the vmstate dumps in JSON format as output by QEMU
with the -dump-vmstate option.

It flags various errors, like version mismatch, sections going away,
size mismatches, etc.

This script is tolerant of a few changes that do not change the on-wire
format, like embedding a few fields within substructs.

The script takes -s/--src and -d/--dest parameters, to which filenames
are given as arguments.

Signed-off-by: Amit Shah <address@hidden>
---
 scripts/vmstate-static-checker.py | 336 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 336 insertions(+)
 create mode 100755 scripts/vmstate-static-checker.py

diff --git a/scripts/vmstate-static-checker.py 
b/scripts/vmstate-static-checker.py
new file mode 100755
index 0000000..13cfac5
--- /dev/null
+++ b/scripts/vmstate-static-checker.py
@@ -0,0 +1,336 @@
+#!/usr/bin/python
+#
+# Compares vmstate information stored in JSON format, obtained from
+# the -dump-vmstate QEMU command.
+#
+# Copyright 2014 Amit Shah <address@hidden>
+# Copyright 2014 Red Hat, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, see <http://www.gnu.org/licenses/>.
+
+import json
+import argparse
+
+def check_fields_match(s_field, d_field):
+    # Some fields changed names between qemu versions.  This list
+    # is used to whitelist such changes.
+    changed_names = ['dev',  'pcidev', 'pci_dev', 'd', \
+                     'parent_obj', 'parent_obj.parent_obj', \
+                     'parent_obj.parent_obj.parent_obj', \
+                     'bridge.dev', 'bridge.dev.shpc', 'shpc', \
+                     'card', 'port.br.dev', 'port.br.dev.exp.aer_log', \
+                     'br.dev', 'br.dev.exp.aer_log', \
+                     'parent_obj.parent_obj.parent_obj.exp.aer_log', \
+                     'parent_obj.parent_obj.exp.aer_log', \
+                     'pci0_status', \
+                     'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]', \
+                     'pci_irq_levels', 'pci_irq_levels_vmstate', \
+                     'num_surfaces', 'ssd.num_surfaces', \
+                     'timer', 'timer_expiry', \
+                     'usb-ptr-queue', 'HIDPointerEventQueue'  ]
+
+    if s_field == d_field:
+        return True
+
+    if s_field in changed_names and d_field in changed_names:
+        return True
+
+    return False
+
+
+def exists_in_substruct(fields, item):
+    # Some QEMU versions moved a few fields inside a substruct.  This
+    # kept the on-wire format the same.  This function checks if
+    # something got shifted inside a substruct.  For example, the
+    # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
+
+    try:
+        desc = fields["Description"]
+    except KeyError:
+        return False
+
+    try:
+        substruct_fields = desc["Fields"]
+    except KeyError:
+        return False
+
+    d_iter = iter(substruct_fields)
+
+    try:
+        d_item = d_iter.next()
+    except StopIteration:
+        return False
+
+    return check_fields_match(d_item["field"], item)
+
+
+def forward_to_substruct(fields):
+    # We don't need to check for failure here as exists_in_substruct()
+    # would have already done that.  This is called only when a
+    # substruct exists.
+
+    desc = fields["Description"]
+    substruct_fields = desc["Fields"]
+
+    return substruct_fields
+
+
+def check_fields(src_fields, dest_fields, desc, sec):
+    # This function checks for all the fields in a section.  If some
+    # fields got embedded into a substruct, this function will also
+    # attempt to check inside the substruct.
+
+    d_iter = iter(dest_fields)
+    s_iter = iter(src_fields)
+
+    # Using these lists as stacks to store previous value of s_iter
+    # and d_iter, so that when time comes to exit out of a substruct,
+    # we can go back one level up and continue from where we left off.
+
+    s_iter_list = []
+    d_iter_list = []
+
+    advance_src = True
+    advance_dest = True
+
+    while True:
+        if advance_src:
+            try:
+                s_item = s_iter.next()
+            except StopIteration:
+                if not s_iter_list:
+                    break
+
+                s_iter = s_iter_list.pop()
+                continue
+        else:
+            # We want to avoid advancing just once -- when entering a
+            # substruct, or when exiting one.
+            advance_src = True
+
+        if advance_dest:
+            try:
+                d_item = d_iter.next()
+            except StopIteration:
+                if not d_iter_list:
+                    # We were not in a substruct
+                    print "Section \"" + sec + "\",",
+                    print "Description " + "\"" + desc + "\":",
+                    print "expected field \"" + s_item["field"] + "\",",
+                    print "while dest has no further fields"
+                    break
+
+                d_iter = d_iter_list.pop()
+                advance_src = False
+                continue
+        else:
+            advance_dest = True
+
+        match_found = check_fields_match(s_item["field"], d_item["field"])
+
+        if not match_found:
+            in_dest_substruct = False
+            in_src_substruct = False
+            # Some fields were put in substructs, keeping the
+            # on-wire format the same, but breaking static tools
+            # like this one.
+
+            # First, check if dest has a new substruct.
+            in_dest_substruct = exists_in_substruct(d_item, s_item["field"])
+            if in_dest_substruct:
+                # listiterators don't have a prev() function, so we
+                # have to store our current location, descend into the
+                # substruct, and ensure we come out as if nothing
+                # happened when the substruct is over.
+                #
+                # Essentially we're opening the substructs that got
+                # added which didn't change the wire format.
+                d_iter_list.append(d_iter)
+                substruct_fields = forward_to_substruct(d_item)
+                d_iter = iter(substruct_fields)
+                advance_src = False
+                continue
+            else:
+                # Next, check if src has substruct that dest removed
+                # (can happen in backward migration: 2.0 -> 1.5)
+                in_src_substruct = exists_in_substruct(s_item, d_item["field"])
+                if in_src_substruct:
+                    s_iter_list.append(s_iter)
+                    substruct_fields = forward_to_substruct(s_item)
+                    s_iter = iter(substruct_fields)
+                    advance_dest = False
+                    continue
+                else:
+                    print "Section \"" + sec + "\",",
+                    print "Description \"" + desc + "\":",
+                    print "expected field \"" + s_item["field"] + "\",",
+                    print "got \"" + d_item["field"] + "\"; skipping rest"
+                    break
+
+        check_version(s_item, d_item, sec, desc)
+
+        try:
+            s_item["Description"]
+        except KeyError:
+            # Check size of this field only if it's not a VMSTRUCT entry
+            check_size(s_item, d_item, sec, desc, s_item["field"])
+
+        check_description_in_list(s_item, d_item, sec, desc)
+
+
+def check_subsections(src_sub, dest_sub, desc, sec):
+    for s_index, s_item in enumerate(src_sub):
+        found = False
+        for d_index, d_item in enumerate(dest_sub):
+            if s_item["name"] != d_item["name"]:
+                continue
+
+            found = True
+            check_descriptions(s_item, d_item, sec)
+
+        if found == False:
+            print "Section \"" + sec + "\", Description \"" + desc + "\":",
+            print "Subsection \"" + s_item["name"] + "\" not found"
+
+
+def check_description_in_list(s_item, d_item, sec, desc):
+    try:
+        s_item["Description"]
+    except KeyError:
+        return
+
+    try:
+        d_item["Description"]
+    except KeyError:
+        print "Section \"" + sec + "\", Description \"" + desc + "\",",
+        print "Field \"" + s_item["field"] + "\": missing description"
+        return
+
+    check_descriptions(s_item["Description"], d_item["Description"], sec)
+
+
+def check_descriptions(src_desc, dest_desc, sec):
+    check_version(src_desc, dest_desc, sec, src_desc["name"])
+
+    if not (check_fields_match(src_desc["name"], dest_desc["name"])):
+        print "Section \"" + sec + "\":",
+        print "Description \"" + src_desc["name"] + "\"",
+        print "missing, got \"" + dest_desc["name"] +"\" instead; skipping"
+        return
+
+    for f in src_desc:
+        try:
+            dest_desc[f]
+        except KeyError:
+            print "Section \"" + sec + "\"",
+            print "Description \"" + src_desc["name"] + "\":",
+            print "Entry \"" + f + "\" missing"
+            continue
+
+        if f == 'Fields':
+            check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
+
+        if f == 'Subsections':
+            check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
+
+
+def check_version(s, d, sec, desc=None):
+    if s["version_id"] > d["version_id"]:
+        print "Section \"" + sec + "\"",
+        if desc:
+            print "Description \"" + desc + "\":",
+        print "version error:", s["version_id"], ">", d["version_id"]
+
+    try:
+        d["minimum_version_id"]
+    except KeyError:
+        return
+
+    if s["version_id"] < d["minimum_version_id"]:
+        print "Section \"" + sec + "\"",
+        if desc:
+            print "Description \"" + desc + "\":",
+            print "minimum version error:", s["version_id"], "<",
+            print d["minimum_version_id"]
+
+
+def check_size(s, d, sec, desc=None, field=None):
+    if s["size"] != d["size"]:
+        print "Section \"" + sec + "\"",
+        if desc:
+            print "Description \"" + desc + "\"",
+        if field:
+            print "Field \"" + field + "\"",
+        print "size mismatch:", s["size"], ",", d["size"]
+
+
+def check_machine_type(s, d, sec):
+    if s["Name"] != d["Name"]:
+        print "Warning: checking incompatible machine types:",
+        print "\"" + s["Name"] + "\", \"" + d["Name"] + "\""
+    return
+
+
+def main():
+    parser = argparse.ArgumentParser(description=
+                                     'Parse vmstate dumps from QEMU.')
+    parser.add_argument('-s', '--src', type=file, required=True,
+                        help='json dump from src qemu')
+    parser.add_argument('-d', '--dest', type=file, required=True,
+                        help='json dump from dest qemu')
+    parser.add_argument('--reverse', required=False, default=False,
+                        action='store_true',
+                        help='reverse the direction')
+    args = parser.parse_args()
+
+    src_data = json.load(args.src)
+    dest_data = json.load(args.dest)
+    args.src.close()
+    args.dest.close()
+
+    if args.reverse == True:
+        temp = src_data
+        src_data = dest_data
+        dest_data = temp
+
+    for sec in src_data:
+        try:
+            dest_data[sec]
+        except KeyError:
+            print "Section \"" + sec + "\" does not exist in dest"
+            continue
+
+        s = src_data[sec]
+        d = dest_data[sec]
+
+        if sec == "vmschkmachine":
+            check_machine_type(s, d, sec);
+            continue;
+
+        check_version(s, d, sec)
+
+        for entry in s:
+            try:
+                d[entry]
+            except KeyError:
+                print "Section \"" + sec + "\": Entry \"" + entry + "\"",
+                print "missing"
+                continue
+
+            if entry == "Description":
+                check_descriptions(s[entry], d[entry], sec)
+
+
+if __name__ == '__main__':
+    main()
-- 
1.9.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]