migration: dump vmstate info as a json file for static analysis
This commit adds a new command, '-dump-vmstate', that takes a filename as an argument. 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> Reviewed-by: Juan Quintela <quintela@redhat.com> Signed-off-by: Juan Quintela <quintela@redhat.com>
This commit is contained in:
parent
e325b49a32
commit
abfd9ce341
|
@ -788,4 +788,6 @@ int64_t self_announce_delay(int round)
|
||||||
return 50 + (SELF_ANNOUNCE_ROUNDS - round - 1) * 100;
|
return 50 + (SELF_ANNOUNCE_ROUNDS - round - 1) * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dump_vmstate_json_to_file(FILE *out_fp);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -3252,6 +3252,20 @@ STEXI
|
||||||
prepend a timestamp to each log message.(default:on)
|
prepend a timestamp to each log message.(default:on)
|
||||||
ETEXI
|
ETEXI
|
||||||
|
|
||||||
|
DEF("dump-vmstate", HAS_ARG, QEMU_OPTION_dump_vmstate,
|
||||||
|
"-dump-vmstate <file>\n"
|
||||||
|
" Output vmstate information in JSON format to file.\n"
|
||||||
|
" Use the scripts/vmstate-static-checker.py file to\n"
|
||||||
|
" check for possible regressions in migration code\n"
|
||||||
|
" by comparing two such vmstate dumps.",
|
||||||
|
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!
|
HXCOMM This is the last statement. Insert new options before this line!
|
||||||
STEXI
|
STEXI
|
||||||
@end table
|
@end table
|
||||||
|
|
139
savevm.c
139
savevm.c
|
@ -24,6 +24,7 @@
|
||||||
|
|
||||||
#include "config-host.h"
|
#include "config-host.h"
|
||||||
#include "qemu-common.h"
|
#include "qemu-common.h"
|
||||||
|
#include "hw/boards.h"
|
||||||
#include "hw/hw.h"
|
#include "hw/hw.h"
|
||||||
#include "hw/qdev.h"
|
#include "hw/qdev.h"
|
||||||
#include "net/net.h"
|
#include "net/net.h"
|
||||||
|
@ -240,6 +241,144 @@ static QTAILQ_HEAD(savevm_handlers, SaveStateEntry) savevm_handlers =
|
||||||
QTAILQ_HEAD_INITIALIZER(savevm_handlers);
|
QTAILQ_HEAD_INITIALIZER(savevm_handlers);
|
||||||
static int global_section_id;
|
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)
|
static int calculate_new_instance_id(const char *idstr)
|
||||||
{
|
{
|
||||||
SaveStateEntry *se;
|
SaveStateEntry *se;
|
||||||
|
|
13
vl.c
13
vl.c
|
@ -2935,6 +2935,7 @@ int main(int argc, char **argv, char **envp)
|
||||||
1024 * 1024;
|
1024 * 1024;
|
||||||
ram_addr_t maxram_size = default_ram_size;
|
ram_addr_t maxram_size = default_ram_size;
|
||||||
uint64_t ram_slots = 0;
|
uint64_t ram_slots = 0;
|
||||||
|
FILE *vmstate_dump_file = NULL;
|
||||||
|
|
||||||
atexit(qemu_run_exit_notifiers);
|
atexit(qemu_run_exit_notifiers);
|
||||||
error_set_progname(argv[0]);
|
error_set_progname(argv[0]);
|
||||||
|
@ -3944,6 +3945,13 @@ int main(int argc, char **argv, char **envp)
|
||||||
}
|
}
|
||||||
configure_msg(opts);
|
configure_msg(opts);
|
||||||
break;
|
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:
|
default:
|
||||||
os_parse_cmd_args(popt->index, optarg);
|
os_parse_cmd_args(popt->index, optarg);
|
||||||
}
|
}
|
||||||
|
@ -4495,6 +4503,11 @@ int main(int argc, char **argv, char **envp)
|
||||||
}
|
}
|
||||||
|
|
||||||
qdev_prop_check_global();
|
qdev_prop_check_global();
|
||||||
|
if (vmstate_dump_file) {
|
||||||
|
/* dump and exit */
|
||||||
|
dump_vmstate_json_to_file(vmstate_dump_file);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (incoming) {
|
if (incoming) {
|
||||||
Error *local_err = NULL;
|
Error *local_err = NULL;
|
||||||
|
|
Loading…
Reference in New Issue