3 # Compares vmstate information stored in JSON format, obtained from
4 # the -dump-vmstate QEMU command.
6 # Copyright 2014 Amit Shah <amit.shah@redhat.com>
7 # Copyright 2014 Red Hat, Inc.
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License along
20 # with this program; if not, see <http://www.gnu.org/licenses/>.
26 # Count the number of errors found
32 # Ensure we don't wrap around or reset to 0 -- the shell only has
33 # an 8-bit return value.
38 def check_fields_match(name
, s_field
, d_field
):
39 if s_field
== d_field
:
42 # Some fields changed names between qemu versions. This list
43 # is used to allow such changes in each section / description.
45 'apic': ['timer', 'timer_expiry'],
46 'e1000': ['dev', 'parent_obj'],
47 'ehci': ['dev', 'pcidev'],
48 'I440FX': ['dev', 'parent_obj'],
49 'ich9_ahci': ['card', 'parent_obj'],
50 'ich9-ahci': ['ahci', 'ich9_ahci'],
51 'ioh3420': ['PCIDevice', 'PCIEDevice'],
52 'ioh-3240-express-root-port': ['port.br.dev',
53 'parent_obj.parent_obj.parent_obj',
54 'port.br.dev.exp.aer_log',
55 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
56 'cirrus_vga': ['hw_cursor_x', 'vga.hw_cursor_x',
57 'hw_cursor_y', 'vga.hw_cursor_y'],
58 'lsiscsi': ['dev', 'parent_obj'],
59 'mch': ['d', 'parent_obj'],
60 'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'],
61 'pcnet': ['pci_dev', 'parent_obj'],
62 'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'],
63 'piix4_pm': ['dev', 'parent_obj', 'pci0_status',
64 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]',
65 'pm1a.sts', 'ar.pm1.evt.sts', 'pm1a.en', 'ar.pm1.evt.en',
66 'pm1_cnt.cnt', 'ar.pm1.cnt.cnt',
67 'tmr.timer', 'ar.tmr.timer',
68 'tmr.overflow_time', 'ar.tmr.overflow_time',
70 'rtl8139': ['dev', 'parent_obj'],
71 'qxl': ['num_surfaces', 'ssd.num_surfaces'],
72 'usb-ccid': ['abProtocolDataStructure', 'abProtocolDataStructure.data'],
73 'usb-host': ['dev', 'parent_obj'],
74 'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
75 'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
76 'vmware_vga': ['card', 'parent_obj'],
77 'vmware_vga_internal': ['depth', 'new_depth'],
78 'xhci': ['pci_dev', 'parent_obj'],
79 'x3130-upstream': ['PCIDevice', 'PCIEDevice'],
80 'xio3130-express-downstream-port': ['port.br.dev',
81 'parent_obj.parent_obj.parent_obj',
82 'port.br.dev.exp.aer_log',
83 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
84 'xio3130-downstream': ['PCIDevice', 'PCIEDevice'],
85 'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
87 'parent_obj.parent_obj.exp.aer_log'],
88 'spapr_pci': ['dma_liobn[0]', 'mig_liobn',
89 'mem_win_addr', 'mig_mem_win_addr',
90 'mem_win_size', 'mig_mem_win_size',
91 'io_win_addr', 'mig_io_win_addr',
92 'io_win_size', 'mig_io_win_size'],
95 if not name
in changed_names
:
98 if s_field
in changed_names
[name
] and d_field
in changed_names
[name
]:
103 def get_changed_sec_name(sec
):
104 # Section names can change -- see commit 292b1634 for an example.
106 "ICH9 LPC": "ICH9-LPC",
107 "e1000-82540em": "e1000",
113 if changes
[item
] == sec
:
117 def exists_in_substruct(fields
, item
):
118 # Some QEMU versions moved a few fields inside a substruct. This
119 # kept the on-wire format the same. This function checks if
120 # something got shifted inside a substruct. For example, the
121 # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
123 if not "Description" in fields
:
126 if not "Fields" in fields
["Description"]:
129 substruct_fields
= fields
["Description"]["Fields"]
131 if substruct_fields
== []:
134 return check_fields_match(fields
["Description"]["name"],
135 substruct_fields
[0]["field"], item
)
137 def size_total(entry
):
139 if "num" not in entry
:
141 return size
* entry
["num"]
143 def check_fields(src_fields
, dest_fields
, desc
, sec
):
144 # This function checks for all the fields in a section. If some
145 # fields got embedded into a substruct, this function will also
146 # attempt to check inside the substruct.
148 d_iter
= iter(dest_fields
)
149 s_iter
= iter(src_fields
)
151 # Using these lists as stacks to store previous value of s_iter
152 # and d_iter, so that when time comes to exit out of a substruct,
153 # we can go back one level up and continue from where we left off.
165 s_item
= next(s_iter
)
166 except StopIteration:
167 if s_iter_list
== []:
170 s_iter
= s_iter_list
.pop()
173 if unused_count
== 0:
174 # We want to avoid advancing just once -- when entering a
175 # dest substruct, or when exiting one.
180 d_item
= next(d_iter
)
181 except StopIteration:
182 if d_iter_list
== []:
183 # We were not in a substruct
184 print("Section \"" + sec
+ "\",", end
=' ')
185 print("Description " + "\"" + desc
+ "\":", end
=' ')
186 print("expected field \"" + s_item
["field"] + "\",", end
=' ')
187 print("while dest has no further fields")
191 d_iter
= d_iter_list
.pop()
195 if unused_count
== 0:
198 if unused_count
!= 0:
199 if advance_dest
== False:
200 unused_count
= unused_count
- s_item
["size"]
201 if unused_count
== 0:
205 print("Section \"" + sec
+ "\",", end
=' ')
206 print("Description \"" + desc
+ "\":", end
=' ')
207 print("unused size mismatch near \"", end
=' ')
208 print(s_item
["field"] + "\"")
213 if advance_src
== False:
214 unused_count
= unused_count
- d_item
["size"]
215 if unused_count
== 0:
219 print("Section \"" + sec
+ "\",", end
=' ')
220 print("Description \"" + desc
+ "\":", end
=' ')
221 print("unused size mismatch near \"", end
=' ')
222 print(d_item
["field"] + "\"")
227 if not check_fields_match(desc
, s_item
["field"], d_item
["field"]):
228 # Some fields were put in substructs, keeping the
229 # on-wire format the same, but breaking static tools
232 # First, check if dest has a new substruct.
233 if exists_in_substruct(d_item
, s_item
["field"]):
234 # listiterators don't have a prev() function, so we
235 # have to store our current location, descend into the
236 # substruct, and ensure we come out as if nothing
237 # happened when the substruct is over.
239 # Essentially we're opening the substructs that got
240 # added which didn't change the wire format.
241 d_iter_list
.append(d_iter
)
242 substruct_fields
= d_item
["Description"]["Fields"]
243 d_iter
= iter(substruct_fields
)
247 # Next, check if src has substruct that dest removed
248 # (can happen in backward migration: 2.0 -> 1.5)
249 if exists_in_substruct(s_item
, d_item
["field"]):
250 s_iter_list
.append(s_iter
)
251 substruct_fields
= s_item
["Description"]["Fields"]
252 s_iter
= iter(substruct_fields
)
256 if s_item
["field"] == "unused" or d_item
["field"] == "unused":
257 s_size
= size_total(s_item
)
258 d_size
= size_total(d_item
)
262 if d_item
["field"] == "unused":
264 unused_count
= d_size
- s_size
;
267 if s_item
["field"] == "unused":
269 unused_count
= s_size
- d_size
272 print("Section \"" + sec
+ "\",", end
=' ')
273 print("Description \"" + desc
+ "\":", end
=' ')
274 print("expected field \"" + s_item
["field"] + "\",", end
=' ')
275 print("got \"" + d_item
["field"] + "\"; skipping rest")
279 check_version(s_item
, d_item
, sec
, desc
)
281 if not "Description" in s_item
:
282 # Check size of this field only if it's not a VMSTRUCT entry
283 check_size(s_item
, d_item
, sec
, desc
, s_item
["field"])
285 check_description_in_list(s_item
, d_item
, sec
, desc
)
288 def check_subsections(src_sub
, dest_sub
, desc
, sec
):
289 for s_item
in src_sub
:
291 for d_item
in dest_sub
:
292 if s_item
["name"] != d_item
["name"]:
296 check_descriptions(s_item
, d_item
, sec
)
299 print("Section \"" + sec
+ "\", Description \"" + desc
+ "\":", end
=' ')
300 print("Subsection \"" + s_item
["name"] + "\" not found")
304 def check_description_in_list(s_item
, d_item
, sec
, desc
):
305 if not "Description" in s_item
:
308 if not "Description" in d_item
:
309 print("Section \"" + sec
+ "\", Description \"" + desc
+ "\",", end
=' ')
310 print("Field \"" + s_item
["field"] + "\": missing description")
314 check_descriptions(s_item
["Description"], d_item
["Description"], sec
)
317 def check_descriptions(src_desc
, dest_desc
, sec
):
318 check_version(src_desc
, dest_desc
, sec
, src_desc
["name"])
320 if not check_fields_match(sec
, src_desc
["name"], dest_desc
["name"]):
321 print("Section \"" + sec
+ "\":", end
=' ')
322 print("Description \"" + src_desc
["name"] + "\"", end
=' ')
323 print("missing, got \"" + dest_desc
["name"] + "\" instead; skipping")
328 if not f
in dest_desc
:
329 print("Section \"" + sec
+ "\"", end
=' ')
330 print("Description \"" + src_desc
["name"] + "\":", end
=' ')
331 print("Entry \"" + f
+ "\" missing")
336 check_fields(src_desc
[f
], dest_desc
[f
], src_desc
["name"], sec
)
338 if f
== 'Subsections':
339 check_subsections(src_desc
[f
], dest_desc
[f
], src_desc
["name"], sec
)
342 def check_version(s
, d
, sec
, desc
=None):
343 if s
["version_id"] > d
["version_id"]:
344 print("Section \"" + sec
+ "\"", end
=' ')
346 print("Description \"" + desc
+ "\":", end
=' ')
347 print("version error:", s
["version_id"], ">", d
["version_id"])
350 if not "minimum_version_id" in d
:
353 if s
["version_id"] < d
["minimum_version_id"]:
354 print("Section \"" + sec
+ "\"", end
=' ')
356 print("Description \"" + desc
+ "\":", end
=' ')
357 print("minimum version error:", s
["version_id"], "<", end
=' ')
358 print(d
["minimum_version_id"])
362 def check_size(s
, d
, sec
, desc
=None, field
=None):
363 if s
["size"] != d
["size"]:
364 print("Section \"" + sec
+ "\"", end
=' ')
366 print("Description \"" + desc
+ "\"", end
=' ')
368 print("Field \"" + field
+ "\"", end
=' ')
369 print("size mismatch:", s
["size"], ",", d
["size"])
373 def check_machine_type(s
, d
):
374 if s
["Name"] != d
["Name"]:
375 print("Warning: checking incompatible machine types:", end
=' ')
376 print("\"" + s
["Name"] + "\", \"" + d
["Name"] + "\"")
380 help_text
= "Parse JSON-formatted vmstate dumps from QEMU in files SRC and DEST. Checks whether migration from SRC to DEST QEMU versions would break based on the VMSTATE information contained within the JSON outputs. The JSON output is created from a QEMU invocation with the -dump-vmstate parameter and a filename argument to it. Other parameters to QEMU do not matter, except the -M (machine type) parameter."
382 parser
= argparse
.ArgumentParser(description
=help_text
)
383 parser
.add_argument('-s', '--src', type=argparse
.FileType('r'),
385 help='json dump from src qemu')
386 parser
.add_argument('-d', '--dest', type=argparse
.FileType('r'),
388 help='json dump from dest qemu')
389 parser
.add_argument('--reverse', required
=False, default
=False,
391 help='reverse the direction')
392 args
= parser
.parse_args()
394 src_data
= json
.load(args
.src
)
395 dest_data
= json
.load(args
.dest
)
406 if not dest_sec
in dest_data
:
407 # Either the section name got changed, or the section
408 # doesn't exist in dest.
409 dest_sec
= get_changed_sec_name(sec
)
410 if not dest_sec
in dest_data
:
411 print("Section \"" + sec
+ "\" does not exist in dest")
416 d
= dest_data
[dest_sec
]
418 if sec
== "vmschkmachine":
419 check_machine_type(s
, d
)
422 check_version(s
, d
, sec
)
426 print("Section \"" + sec
+ "\": Entry \"" + entry
+ "\"", end
=' ')
431 if entry
== "Description":
432 check_descriptions(s
[entry
], d
[entry
], sec
)
437 if __name__
== '__main__':