From: Amit Shah <amit.shah@redhat.com>
To: qemu list <qemu-devel@nongnu.org>
Cc: "Juan Quintela" <quintela@redhat.com>,
"Markus Armbruster" <armbru@redhat.com>,
"Alexander Graf" <agraf@suse.de>,
"\\\"Dr. David Alan Gilbert\\\"" <dgilbert@redhat.com>,
"Amit Shah" <amit.shah@redhat.com>,
"Paolo Bonzini" <pbonzini@redhat.com>,
"Andreas Färber" <afaerber@suse.de>
Subject: [Qemu-devel] [PATCH 02/18] vmstate-static-checker: script to validate vmstate changes
Date: Mon, 12 May 2014 16:46:02 +0530 [thread overview]
Message-ID: <65b66b690837cae1839a9e89cf9e2164814d58c7.1399892389.git.amit.shah@redhat.com> (raw)
In-Reply-To: <cover.1399892389.git.amit.shah@redhat.com>
In-Reply-To: <cover.1399892389.git.amit.shah@redhat.com>
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 <amit.shah@redhat.com>
---
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 <amit.shah@redhat.com>
+# 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
next prev parent reply other threads:[~2014-05-12 11:17 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-05-12 11:16 [Qemu-devel] [PATCH 00/18] migration: add static analysis tool to check vmstate compat between versions Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 01/18] migration: dump vmstate info as a json file for static analysis Amit Shah
2014-05-12 12:51 ` Eric Blake
2014-05-13 4:12 ` Amit Shah
2014-05-21 11:45 ` Eric Blake
2014-05-21 9:44 ` Dr. David Alan Gilbert
2014-05-21 9:55 ` Amit Shah
2014-05-21 10:03 ` Dr. David Alan Gilbert
2014-05-21 10:12 ` Amit Shah
2014-05-21 11:47 ` Markus Armbruster
2014-05-21 11:45 ` Markus Armbruster
2014-05-12 11:16 ` Amit Shah [this message]
2014-05-12 11:16 ` [Qemu-devel] [PATCH 03/18] tests: vmstate static checker: add dump1 and dump2 files Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 04/18] tests: vmstate static checker: incompat machine types Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 05/18] tests: vmstate static checker: add version error in main section Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 06/18] tests: vmstate static checker: version mismatch inside a Description Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 07/18] tests: vmstate static checker: minimum_version_id check Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 08/18] tests: vmstate static checker: remove a section Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 09/18] tests: vmstate static checker: remove a field Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 10/18] tests: vmstate static checker: remove last field in a struct Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 11/18] tests: vmstate static checker: change description name Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 12/18] tests: vmstate static checker: remove Fields Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 13/18] tests: vmstate static checker: remove Description Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 14/18] tests: vmstate static checker: remove Description inside Fields Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 15/18] tests: vmstate static checker: remove a subsection Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 16/18] tests: vmstate static checker: remove Subsections Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 17/18] tests: vmstate static checker: add substructure for usb-kbd for hid section Amit Shah
2014-05-12 11:16 ` [Qemu-devel] [PATCH 18/18] tests: vmstate static checker: add size mismatch inside substructure Amit Shah
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=65b66b690837cae1839a9e89cf9e2164814d58c7.1399892389.git.amit.shah@redhat.com \
--to=amit.shah@redhat.com \
--cc=afaerber@suse.de \
--cc=agraf@suse.de \
--cc=armbru@redhat.com \
--cc=dgilbert@redhat.com \
--cc=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
--cc=quintela@redhat.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).