[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH v2 02/18] vmstate-static-checker: script to validate
From: |
Amit Shah |
Subject: |
[Qemu-devel] [PATCH v2 02/18] vmstate-static-checker: script to validate vmstate changes |
Date: |
Fri, 6 Jun 2014 14:39:34 +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 | 304 ++++++++++++++++++++++++++++++++++++++
1 file changed, 304 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..c24e8bb
--- /dev/null
+++ b/scripts/vmstate-static-checker.py
@@ -0,0 +1,304 @@
+#!/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):
+ if s_field == d_field:
+ return True
+
+ # Some fields changed names between qemu versions. This list
+ # is used to whitelist such changes.
+ changed_names = [
+ ['d', 'dev', 'pcidev', 'pci_dev', 'parent_obj'],
+ ['card', 'parent_obj'],
+ ['bridge.dev', 'parent_obj'],
+ ['br.dev', 'parent_obj.parent_obj'],
+ ['port.br.dev', 'parent_obj.parent_obj.parent_obj'],
+ ['port.br.dev.exp.aer_log',
+ 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
+ ['br.dev.exp.aer_log',
+ 'parent_obj.parent_obj.exp.aer_log'],
+ ['shpc', 'bridge.dev.shpc'],
+ ['pci0_status',
+ 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]'],
+ ['pci_irq_levels', 'pci_irq_levels_vmstate'],
+ ['usb-ptr-queue', 'HIDPointerEventQueue'],
+ ['num_surfaces', 'ssd.num_surfaces'],
+ ['timer', 'timer_expiry'],
+ ]
+
+ for grp in changed_names:
+ if s_field in grp and d_field in grp:
+ 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
+
+ if not "Description" in fields:
+ return False
+
+ if not "Fields" in fields["Description"]:
+ return False
+
+ substruct_fields = fields["Description"]["Fields"]
+
+ if substruct_fields == []:
+ return False
+
+ return check_fields_match(substruct_fields[0]["field"], item)
+
+
+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 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 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
+
+ if not check_fields_match(s_item["field"], d_item["field"]):
+ # 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.
+ if exists_in_substruct(d_item, s_item["field"]):
+ # 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 = d_item["Description"]["Fields"]
+ 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)
+ if exists_in_substruct(s_item, d_item["field"]):
+ s_iter_list.append(s_iter)
+ substruct_fields = s_item["Description"]["Fields"]
+ 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)
+
+ if not "Description" in s_item:
+ # 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_item in src_sub:
+ found = False
+ for d_item in dest_sub:
+ if s_item["name"] != d_item["name"]:
+ continue
+
+ found = True
+ check_descriptions(s_item, d_item, sec)
+
+ if not found:
+ print "Section \"" + sec + "\", Description \"" + desc + "\":",
+ print "Subsection \"" + s_item["name"] + "\" not found"
+
+
+def check_description_in_list(s_item, d_item, sec, desc):
+ if not "Description" in s_item:
+ return
+
+ if not "Description" in d_item:
+ 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:
+ if not f in dest_desc:
+ 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"]
+
+ if not "minimum_version_id" in d:
+ 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):
+ 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:
+ temp = src_data
+ src_data = dest_data
+ dest_data = temp
+
+ for sec in src_data:
+ if not sec in dest_data:
+ 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)
+ continue
+
+ check_version(s, d, sec)
+
+ for entry in s:
+ if not entry in d:
+ print "Section \"" + sec + "\": Entry \"" + entry + "\"",
+ print "missing"
+ continue
+
+ if entry == "Description":
+ check_descriptions(s[entry], d[entry], sec)
+
+
+if __name__ == '__main__':
+ main()
--
1.9.3
- [Qemu-devel] [PATCH v2 00/18] migration: add static analysis tool to check vmstate compat, Amit Shah, 2014/06/06
- [Qemu-devel] [PATCH v2 01/18] migration: dump vmstate info as a json file for static analysis, Amit Shah, 2014/06/06
- [Qemu-devel] [PATCH v2 02/18] vmstate-static-checker: script to validate vmstate changes,
Amit Shah <=
- [Qemu-devel] [PATCH v2 03/18] tests: vmstate static checker: add dump1 and dump2 files, Amit Shah, 2014/06/06
- [Qemu-devel] [PATCH v2 04/18] tests: vmstate static checker: incompat machine types, Amit Shah, 2014/06/06
- [Qemu-devel] [PATCH v2 05/18] tests: vmstate static checker: add version error in main section, Amit Shah, 2014/06/06
- [Qemu-devel] [PATCH v2 06/18] tests: vmstate static checker: version mismatch inside a Description, Amit Shah, 2014/06/06
- [Qemu-devel] [PATCH v2 07/18] tests: vmstate static checker: minimum_version_id check, Amit Shah, 2014/06/06
- [Qemu-devel] [PATCH v2 08/18] tests: vmstate static checker: remove a section, Amit Shah, 2014/06/06
- [Qemu-devel] [PATCH v2 10/18] tests: vmstate static checker: remove last field in a struct, Amit Shah, 2014/06/06
- [Qemu-devel] [PATCH v2 09/18] tests: vmstate static checker: remove a field, Amit Shah, 2014/06/06
- [Qemu-devel] [PATCH v2 11/18] tests: vmstate static checker: change description name, Amit Shah, 2014/06/06
- [Qemu-devel] [PATCH v2 12/18] tests: vmstate static checker: remove Fields, Amit Shah, 2014/06/06