diff mbox

[v2] Introduce "info migrate-times" monitor command

Message ID 1310637335-15020-1-git-send-email-minovotn@redhat.com
State New
Headers show

Commit Message

Michal Novotny July 14, 2011, 9:55 a.m. UTC
Hi,
this is the implementation of the "info migrate-times" command
I did to get the times for the migration to get times for each
migration stage. Based on the fact migration itself is just the
vmsave on the source host and vmload on destination host this
function can be also useful to get the save times however it's
main purpose is measuring the migration times therefore it's
called "info migrate-times". The total memory transferred
during the last migration is being tracked there as well as
total migration time, time of waiting for input data, times
for various migration stages for total value, disk (if
applicable) and ram memory transfer. There's also the time

Comments

Paolo Bonzini July 14, 2011, 10:47 a.m. UTC | #1
On 07/14/2011 11:55 AM, Michal Novotny wrote:
> +/* Time measuring facility */
> +extern int time_measurement_type;
> +extern uint64_t time_saveram1;
> +extern uint64_t time_saveram2;
> +extern uint64_t time_saveram3;
> +extern uint64_t time_savedisk1;
> +extern uint64_t time_savedisk2;
> +extern uint64_t time_savedisk3;
> +extern uint64_t time_savetotal1;
> +extern uint64_t time_savetotal2;
> +extern uint64_t time_savetotal3;

Arrays, please. :)  Or perhaps you can store this information directly 
into a QDict.  I'm not sure, read until the end.

> +extern uint64_t time_savewait_input0;
> +extern uint64_t time_save_measured;
> +extern uint64_t time_loadpart1;
> +extern uint64_t time_loadpart2;
> +extern uint64_t time_loadpart3;
> +extern uint64_t time_loadpart4;
> +extern uint64_t time_load_measured;
> +
> +#define TIME_MEASUREMENT_NONE  0x00
> +#define TIME_MEASUREMENT_SAVE  0x01
> +#define TIME_MEASUREMENT_LOAD  0x02
> +#define TIME_GET(type, name, stage) time_##type##name##stage
> +#define TIME_SET(type, name, stage, tv) time_##type##name##stage = tv
> +#define TIME_ADD(type, name, stage, tv) time_##type##name##stage += tv

This forces type/name/stage to be fixed, so it is worse than before; at 
this point you could remove the macros altogether.

What I disliked in v1 was:

1) this part (duplicated twice, in time_set and time_add)

     if (strcmp(name, "ram") == 0)
         time_set_ram(stage, tv);
     if (strcmp(name, "disk") == 0)
         time_set_disk(stage, tv);
     if (strcmp(name, "wait-input") == 0)
         time_save_wait_input = tv;
     if (strcmp(name, "total") == 0)
         time_set_total(stage, tv);

2) even more shotgun cut-and-paste in the small functions

+static void time_set_ram(int stage, uint64_t tv)
+{
+    if ((stage < 0) || (stage > 3))
+        return;
+
+    time_save_ram[stage - 1] = tv;
+}
+
+static void time_set_disk(int stage, uint64_t tv)
+{
+    if ((stage < 0) || (stage > 3))
+        return;
+
+    time_save_disk[stage - 1] = tv;
+}
+

etc.  (By the way, the test on stage should be <= 0 for all of them!).

3) the fact that you put this in vl.c


Whenever you have duplication like that, you probably should use a more 
generic data structure, or not use any data structure at all (just 
variables).  In the latter case, no funky macros or nothing---just 
variables.

My first thought was to inline everything, but given how v2 looks like, 
perhaps the abstraction was actually good to have---just the 
implementation was ugly---and my judgement was wrong.

Perhaps you can store everything from the beginning in the QDict that 
you will use for the monitor command?

Paolo
diff mbox

Patch

difference which is the inaccuracy value which is caused by
block device flushing and also the qemu_get_clock_ns() is
being used in there and subsequent calls of this function
may result into minor inaccuracies (in the matter smaller than
of milliseconds).

I also did the testing with various migration speed settings
(using the set_migrate_speed monitor command) for 7 GiB
RHEL-6 i386 guest running bonnie++ test for 14 GiB (2x RAM)
and the results were as follows:

Max.speed     |    Memory transferred       |    Time (s)
--------------+-----------------------------+----------------
     32m      |       12 925 676 bytes      |      199 s
     64m      |        7 745 224 bytes      |       26 s
    128m      |        7 674 188 bytes      |       16 s
    256m      |        7 628 988 bytes      |       16 s
    512m      |        7 599 837 bytes      |       15 s
  1024m (1g)  |        7 592 934 bytes      |       14 s
     10g      |        7 583 824 bytes      |       13 s

This has been tested on the 1 GiB network using the remote
migration.

Differences between v1 and v2:
  - Time functions have been implemented as macros instead
  - Handling for both save and load code has been implemented
  - Monitor command implementation moved to savevm.c source file

So please review. This patch could be useful for getting
the migration stage times.

Thanks,
Michal

Signed-off-by: Michal Novotny <minovotn@redhat.com>
---
 arch_init.c       |   22 +++++-
 block-migration.c |   12 +++
 hmp-commands.hx   |    2 +
 migration.c       |   19 +++++
 migration.h       |   30 +++++++
 monitor.c         |    8 ++
 savevm.c          |  234 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 7 files changed, 318 insertions(+), 9 deletions(-)

diff --git a/arch_init.c b/arch_init.c
index 484b39d..d58adb8 100644
--- a/arch_init.c
+++ b/arch_init.c
@@ -252,8 +252,12 @@  int ram_save_live(Monitor *mon, QEMUFile *f, int stage, void *opaque)
 {
     ram_addr_t addr;
     uint64_t bytes_transferred_last;
+    uint64_t t_start;
     double bwidth = 0;
     uint64_t expected_time = 0;
+    int retval = 0;
+
+    t_start = qemu_get_clock_ns(host_clock);
 
     if (stage < 0) {
         cpu_physical_memory_set_dirty_tracking(0);
@@ -272,6 +276,10 @@  int ram_save_live(Monitor *mon, QEMUFile *f, int stage, void *opaque)
         last_offset = 0;
         sort_ram_list();
 
+        TIME_SET(save, ram, 1, 0);
+        TIME_SET(save, ram, 2, 0);
+        TIME_SET(save, ram, 3, 0);
+
         /* Make sure all dirty bits are set */
         QLIST_FOREACH(block, &ram_list.blocks, next) {
             for (addr = block->offset; addr < block->offset + block->length;
@@ -331,8 +339,18 @@  int ram_save_live(Monitor *mon, QEMUFile *f, int stage, void *opaque)
     qemu_put_be64(f, RAM_SAVE_FLAG_EOS);
 
     expected_time = ram_save_remaining() * TARGET_PAGE_SIZE / bwidth;
-
-    return (stage == 2) && (expected_time <= migrate_max_downtime());
+    retval = (stage == 2) && (expected_time <= migrate_max_downtime());
+
+    if (stage == 1)
+        TIME_ADD(save, ram, 1, qemu_get_clock_ns(host_clock) - t_start);
+    else
+    if (stage == 2)
+        TIME_ADD(save, ram, 2, qemu_get_clock_ns(host_clock) - t_start);
+    else
+    if (stage == 3)
+        TIME_ADD(save, ram, 3, qemu_get_clock_ns(host_clock) - t_start);
+
+    return retval;
 }
 
 static inline void *host_from_stream_offset(QEMUFile *f,
diff --git a/block-migration.c b/block-migration.c
index 0936c7d..d154702 100644
--- a/block-migration.c
+++ b/block-migration.c
@@ -17,6 +17,8 @@ 
 #include "qemu-queue.h"
 #include "qemu-timer.h"
 #include "monitor.h"
+#include "qemu-timer.h"
+#include "sysemu.h"
 #include "block-migration.h"
 #include "migration.h"
 #include "blockdev.h"
@@ -556,6 +558,7 @@  static void blk_mig_cleanup(Monitor *mon)
 
 static int block_save_live(Monitor *mon, QEMUFile *f, int stage, void *opaque)
 {
+    uint64_t t_start = qemu_get_clock_ns(host_clock);
     DPRINTF("Enter save live stage %d submitted %d transferred %d\n",
             stage, block_mig_state.submitted, block_mig_state.transferred);
 
@@ -633,6 +636,15 @@  static int block_save_live(Monitor *mon, QEMUFile *f, int stage, void *opaque)
 
     qemu_put_be64(f, BLK_MIG_FLAG_EOS);
 
+    if (stage == 1)
+        TIME_ADD(save, disk, 1, qemu_get_clock_ns(host_clock) - t_start);
+    else
+    if (stage == 2)
+        TIME_ADD(save, disk, 2, qemu_get_clock_ns(host_clock) - t_start);
+    else
+    if (stage == 3)
+        TIME_ADD(save, disk, 3, qemu_get_clock_ns(host_clock) - t_start);
+
     return ((stage == 2) && is_stage2_completed());
 }
 
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 6ad8806..83b33c8 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -1345,6 +1345,8 @@  show CPU statistics
 show user network stack connection states
 @item info migrate
 show migration status
+@item info migrate-times
+show migration times for last non-active migration
 @item info balloon
 show balloon information
 @item info qtree
diff --git a/migration.c b/migration.c
index af3a1f2..2832d54 100644
--- a/migration.c
+++ b/migration.c
@@ -20,6 +20,7 @@ 
 #include "qemu_socket.h"
 #include "block-migration.h"
 #include "qemu-objects.h"
+#include "qemu-timer.h"
 
 //#define DEBUG_MIGRATION
 
@@ -39,6 +40,9 @@  static MigrationState *current_migration;
 static NotifierList migration_state_notifiers =
     NOTIFIER_LIST_INITIALIZER(migration_state_notifiers);
 
+/* Time of last put_ready state */
+static uint64_t t_last_put_ready;
+
 int qemu_start_incoming_migration(const char *uri)
 {
     const char *p;
@@ -63,6 +67,10 @@  int qemu_start_incoming_migration(const char *uri)
 
 void process_incoming_migration(QEMUFile *f)
 {
+    uint64_t t_start;
+
+    t_start = qemu_get_clock_ns(host_clock);
+
     if (qemu_loadvm_state(f) < 0) {
         fprintf(stderr, "load of migration failed\n");
         exit(0);
@@ -70,6 +78,8 @@  void process_incoming_migration(QEMUFile *f)
     qemu_announce_self();
     DPRINTF("successfully loaded vm state\n");
 
+    time_load_measured = qemu_get_clock_ns(host_clock) - t_start;
+
     incoming_expected = false;
 
     if (autostart)
@@ -344,6 +354,11 @@  void migrate_fd_connect(FdMigrationState *s)
 {
     int ret;
 
+    TIME_SET(save, total, 1, 0);
+    TIME_SET(save, total, 2, 0);
+    TIME_SET(save, total, 3, 0);
+    TIME_SET(save, wait_input, 0, 0);
+
     s->file = qemu_fopen_ops_buffered(s,
                                       s->bandwidth_limit,
                                       migrate_fd_put_buffer,
@@ -352,6 +367,7 @@  void migrate_fd_connect(FdMigrationState *s)
                                       migrate_fd_close);
 
     DPRINTF("beginning savevm\n");
+    t_last_put_ready = qemu_get_clock_ns(host_clock);
     ret = qemu_savevm_state_begin(s->mon, s->file, s->mig_state.blk,
                                   s->mig_state.shared);
     if (ret < 0) {
@@ -373,6 +389,7 @@  void migrate_fd_put_ready(void *opaque)
     }
 
     DPRINTF("iterate\n");
+    TIME_ADD(save, wait_input, 0, qemu_get_clock_ns(host_clock) - t_last_put_ready);
     if (qemu_savevm_state_iterate(s->mon, s->file) == 1) {
         int state;
         int old_vm_running = vm_running;
@@ -397,6 +414,8 @@  void migrate_fd_put_ready(void *opaque)
         s->state = state;
         notifier_list_notify(&migration_state_notifiers);
     }
+
+    t_last_put_ready = qemu_get_clock_ns(host_clock);
 }
 
 int migrate_fd_get_status(MigrationState *mig_state)
diff --git a/migration.h b/migration.h
index 050c56c..5d835cf 100644
--- a/migration.h
+++ b/migration.h
@@ -70,6 +70,10 @@  void do_info_migrate_print(Monitor *mon, const QObject *data);
 
 void do_info_migrate(Monitor *mon, QObject **ret_data);
 
+void do_info_migrate_times_print(Monitor *mon, const QObject *data);
+
+void do_info_migrate_times(Monitor *mon, QObject **ret_data);
+
 int exec_start_incoming_migration(const char *host_port);
 
 MigrationState *exec_start_outgoing_migration(Monitor *mon,
@@ -148,4 +152,30 @@  int ram_load(QEMUFile *f, void *opaque, int version_id);
 
 extern int incoming_expected;
 
+/* Time measuring facility */
+extern int time_measurement_type;
+extern uint64_t time_saveram1;
+extern uint64_t time_saveram2;
+extern uint64_t time_saveram3;
+extern uint64_t time_savedisk1;
+extern uint64_t time_savedisk2;
+extern uint64_t time_savedisk3;
+extern uint64_t time_savetotal1;
+extern uint64_t time_savetotal2;
+extern uint64_t time_savetotal3;
+extern uint64_t time_savewait_input0;
+extern uint64_t time_save_measured;
+extern uint64_t time_loadpart1;
+extern uint64_t time_loadpart2;
+extern uint64_t time_loadpart3;
+extern uint64_t time_loadpart4;
+extern uint64_t time_load_measured;
+
+#define TIME_MEASUREMENT_NONE  0x00
+#define TIME_MEASUREMENT_SAVE  0x01
+#define TIME_MEASUREMENT_LOAD  0x02
+#define TIME_GET(type, name, stage) time_##type##name##stage
+#define TIME_SET(type, name, stage, tv) time_##type##name##stage = tv
+#define TIME_ADD(type, name, stage, tv) time_##type##name##stage += tv
+
 #endif
diff --git a/monitor.c b/monitor.c
index 67ceb46..ac555f3 100644
--- a/monitor.c
+++ b/monitor.c
@@ -3060,6 +3060,14 @@  static const mon_cmd_t info_cmds[] = {
         .mhandler.info_new = do_info_migrate,
     },
     {
+        .name       = "migrate-times",
+        .args_type  = "",
+        .params     = "",
+        .help       = "show migration times",
+        .user_print = do_info_migrate_times_print,
+        .mhandler.info_new = do_info_migrate_times,
+    },
+    {
         .name       = "balloon",
         .args_type  = "",
         .params     = "",
diff --git a/savevm.c b/savevm.c
index 8139bc7..d200a45 100644
--- a/savevm.c
+++ b/savevm.c
@@ -82,6 +82,8 @@ 
 #include "qemu_socket.h"
 #include "qemu-queue.h"
 #include "cpus.h"
+#include "qjson.h"
+#include "qint.h"
 
 #define SELF_ANNOUNCE_ROUNDS 5
 
@@ -92,6 +94,24 @@ 
 #define ARP_PTYPE_IP 0x0800
 #define ARP_OP_REQUEST_REV 0x3
 
+int time_measurement_type = TIME_MEASUREMENT_NONE;
+uint64_t time_saveram1;
+uint64_t time_saveram2;
+uint64_t time_saveram3;
+uint64_t time_savedisk1;
+uint64_t time_savedisk2;
+uint64_t time_savedisk3;
+uint64_t time_savetotal1;
+uint64_t time_savetotal2;
+uint64_t time_savetotal3;
+uint64_t time_savewait_input0;
+uint64_t time_save_measured;
+uint64_t time_loadpart1;
+uint64_t time_loadpart2;
+uint64_t time_loadpart3;
+uint64_t time_loadpart4;
+uint64_t time_load_measured;
+
 static int announce_self_create(uint8_t *buf,
 				uint8_t *mac_addr)
 {
@@ -1447,6 +1467,8 @@  static void vmstate_save(QEMUFile *f, SaveStateEntry *se)
 #define QEMU_VM_SECTION_FULL         0x04
 #define QEMU_VM_SUBSECTION           0x05
 
+static uint64_t t_save_start;
+
 bool qemu_savevm_state_blocked(Monitor *mon)
 {
     SaveStateEntry *se;
@@ -1465,6 +1487,10 @@  int qemu_savevm_state_begin(Monitor *mon, QEMUFile *f, int blk_enable,
                             int shared)
 {
     SaveStateEntry *se;
+    int ret = 0;
+
+    time_measurement_type = TIME_MEASUREMENT_SAVE;
+    t_save_start = qemu_get_clock_ns(host_clock);
 
     QTAILQ_FOREACH(se, &savevm_handlers, entry) {
         if(se->set_params == NULL) {
@@ -1472,7 +1498,7 @@  int qemu_savevm_state_begin(Monitor *mon, QEMUFile *f, int blk_enable,
 	}
 	se->set_params(blk_enable, shared, se->opaque);
     }
-    
+
     qemu_put_be32(f, QEMU_VM_FILE_MAGIC);
     qemu_put_be32(f, QEMU_VM_FILE_VERSION);
 
@@ -1499,17 +1525,21 @@  int qemu_savevm_state_begin(Monitor *mon, QEMUFile *f, int blk_enable,
 
     if (qemu_file_has_error(f)) {
         qemu_savevm_state_cancel(mon, f);
-        return -EIO;
+        ret = -EIO;
     }
 
-    return 0;
+    TIME_ADD(save, total, 1, qemu_get_clock_ns(host_clock) - t_save_start);
+    return ret;
 }
 
 int qemu_savevm_state_iterate(Monitor *mon, QEMUFile *f)
 {
     SaveStateEntry *se;
+    uint64_t t_start;
     int ret = 1;
 
+    t_start = qemu_get_clock_ns(host_clock);
+
     QTAILQ_FOREACH(se, &savevm_handlers, entry) {
         if (se->save_live_state == NULL)
             continue;
@@ -1528,20 +1558,30 @@  int qemu_savevm_state_iterate(Monitor *mon, QEMUFile *f)
         }
     }
 
-    if (ret)
-        return 1;
+    if (ret) {
+        ret = 1;
+        goto out;
+    }
 
     if (qemu_file_has_error(f)) {
         qemu_savevm_state_cancel(mon, f);
-        return -EIO;
+        ret = -EIO;
     }
+    else
+        ret = 0;
 
-    return 0;
+out:
+    TIME_ADD(save, total, 2, qemu_get_clock_ns(host_clock) - t_start);
+    return ret;
 }
 
 int qemu_savevm_state_complete(Monitor *mon, QEMUFile *f)
 {
     SaveStateEntry *se;
+    uint64_t t_start;
+    uint64_t t_help;
+
+    t_start = qemu_get_clock_ns(host_clock);
 
     cpu_synchronize_all_states();
 
@@ -1582,6 +1622,10 @@  int qemu_savevm_state_complete(Monitor *mon, QEMUFile *f)
     if (qemu_file_has_error(f))
         return -EIO;
 
+    t_help = qemu_get_clock_ns(host_clock);
+    time_save_measured = t_help - t_save_start;
+    TIME_ADD(save, total, 3, t_help - t_start);
+
     return 0;
 }
 
@@ -1589,6 +1633,13 @@  void qemu_savevm_state_cancel(Monitor *mon, QEMUFile *f)
 {
     SaveStateEntry *se;
 
+    TIME_SET(save, ram, 1, 0);
+    TIME_SET(save, ram, 2, 0);
+    TIME_SET(save, ram, 3, 0);
+    TIME_SET(save, disk, 1, 0);
+    TIME_SET(save, disk, 2, 0);
+    TIME_SET(save, disk, 3, 0);
+
     QTAILQ_FOREACH(se, &savevm_handlers, entry) {
         if (se->save_live_state) {
             se->save_live_state(mon, f, -1, se->opaque);
@@ -1732,6 +1783,7 @@  int qemu_loadvm_state(QEMUFile *f)
     LoadStateEntry *le, *new_le;
     uint8_t section_type;
     unsigned int v;
+    uint64_t t_start;
     int ret;
 
     if (qemu_savevm_state_blocked(default_mon)) {
@@ -1750,12 +1802,17 @@  int qemu_loadvm_state(QEMUFile *f)
     if (v != QEMU_VM_FILE_VERSION)
         return -ENOTSUP;
 
+    time_measurement_type = TIME_MEASUREMENT_LOAD;
+    t_start = qemu_get_clock_ns(host_clock);
+
     while ((section_type = qemu_get_byte(f)) != QEMU_VM_EOF) {
         uint32_t instance_id, version_id, section_id;
+        uint64_t t_start_part;
         SaveStateEntry *se;
         char idstr[257];
         int len;
 
+        t_start_part = qemu_get_clock_ns(host_clock);
         switch (section_type) {
         case QEMU_VM_SECTION_START:
         case QEMU_VM_SECTION_FULL:
@@ -1797,6 +1854,12 @@  int qemu_loadvm_state(QEMUFile *f)
                         instance_id, idstr);
                 goto out;
             }
+
+            if (section_type == QEMU_VM_SECTION_START)
+                TIME_ADD(load, part, 1, qemu_get_clock_ns(host_clock) - t_start_part);
+            else
+                TIME_ADD(load, part, 4, qemu_get_clock_ns(host_clock) - t_start_part);
+
             break;
         case QEMU_VM_SECTION_PART:
         case QEMU_VM_SECTION_END:
@@ -1819,6 +1882,12 @@  int qemu_loadvm_state(QEMUFile *f)
                         section_id);
                 goto out;
             }
+
+            if (section_type == QEMU_VM_SECTION_PART)
+                TIME_ADD(load, part, 2, qemu_get_clock_ns(host_clock) - t_start_part);
+            else
+                TIME_ADD(load, part, 3, qemu_get_clock_ns(host_clock) - t_start_part);
+
             break;
         default:
             fprintf(stderr, "Unknown savevm section type %d\n", section_type);
@@ -1827,6 +1896,8 @@  int qemu_loadvm_state(QEMUFile *f)
         }
     }
 
+    time_load_measured = qemu_get_clock_ns(host_clock) - t_start;
+
     cpu_synchronize_all_post_init();
 
     ret = 0;
@@ -2177,3 +2248,152 @@  void do_info_snapshots(Monitor *mon)
     qemu_free(available_snapshots);
 
 }
+
+/* Monitor command implementation to get the load/save stage times */
+static void migrate_times_print_status(Monitor *mon, const char *name,
+                                       const QDict *status_dict)
+{
+    QDict *qdict;
+    qdict = qobject_to_qdict(qdict_get(status_dict, name));
+
+    monitor_printf(mon, "Times for %s stage 1: %.6f s\n", name,
+                        qdict_get_int(qdict, "stage1") / 1000000.);
+    monitor_printf(mon, "Times for %s stage 2: %.6f s\n", name,
+                        qdict_get_int(qdict, "stage2") / 1000000.);
+    monitor_printf(mon, "Times for %s stage 3: %.6f s\n", name,
+                        qdict_get_int(qdict, "stage3") / 1000000.);
+    monitor_printf(mon, "Times for %s total: %.6f s\n", name,
+                        qdict_get_int(qdict, "total") / 1000000.);
+}
+
+void do_info_migrate_times_print(Monitor *mon, const QObject *data)
+{
+    QDict *qdict;
+
+    if (!time_save_measured && !time_load_measured) {
+        monitor_printf(mon, "Cannot get result times when action is still running\n");
+        return;
+    }
+
+    qdict = qobject_to_qdict(data);
+
+    monitor_printf(mon, "Measured values type: %s\n",
+                   qdict_get_str(qdict, "type"));
+
+    monitor_printf(mon, "Total transferred memory: %"PRIu64" kbytes\n",
+                   qdict_get_int(qdict, "transferred-memory") >> 10);
+
+    monitor_printf(mon, "Total process time: %.6f s\n",
+                   qdict_get_int(qdict, "total-time") / 1000000.);
+
+    if (time_measurement_type == TIME_MEASUREMENT_SAVE) {
+        monitor_printf(mon, "Waiting for input data: %.6f s\n",
+                       qdict_get_int(qdict, "wait-input") / 1000000.);
+
+        if (qdict_haskey(qdict, "total")) {
+            QDict *qdict2;
+            qdict2 = qobject_to_qdict(qdict_get(qdict, "total"));
+
+            monitor_printf(mon, "Time difference (inaccuracy): %.6f s\n",
+                           abs(qdict_get_int(qdict, "migration-time") -
+                            qdict_get_int(qdict, "wait-input") -
+                            qdict_get_int(qdict2, "total")) / 1000000.);
+
+            migrate_times_print_status(mon, "total", qdict);
+        }
+
+        if (qdict_haskey(qdict, "ram")) {
+            migrate_times_print_status(mon, "ram", qdict);
+        }
+
+        if (qdict_haskey(qdict, "disk")) {
+            migrate_times_print_status(mon, "disk", qdict);
+        }
+    }
+    else
+    if (time_measurement_type == TIME_MEASUREMENT_LOAD) {
+        monitor_printf(mon, "Load part 1: %.6f s\n",
+                       qdict_get_int(qdict, "part1") / 1000000.);
+        monitor_printf(mon, "Load part 2: %.6f s\n",
+                       qdict_get_int(qdict, "part2") / 1000000.);
+        monitor_printf(mon, "Load part 3: %.6f s\n",
+                       qdict_get_int(qdict, "part3") / 1000000.);
+        monitor_printf(mon, "Load part 4: %.6f s\n",
+                       qdict_get_int(qdict, "part4") / 1000000.);
+    }
+    else {
+        monitor_printf(mon, "Error: Invalid time measurement type 0x%02x\n", time_measurement_type);
+    }
+}
+
+static void migrate_times_put_status(QDict *qdict, const char *name)
+{
+    QObject *obj;
+    uint64_t t0 = 0;
+    uint64_t t1 = 0;
+    uint64_t t2 = 0;
+    uint64_t t3 = 0;
+
+    if (strcmp(name, "ram") == 0) {
+        t1 = TIME_GET(save, ram, 1) / 1000;
+        t2 = TIME_GET(save, ram, 2) / 1000;
+        t3 = TIME_GET(save, ram, 3) / 1000;
+    }
+    else
+    if (strcmp(name, "disk") == 0) {
+        t1 = TIME_GET(save, disk, 1) / 1000;
+        t2 = TIME_GET(save, disk, 2) / 1000;
+        t3 = TIME_GET(save, disk, 3) / 1000;
+    }
+    else
+    if (strcmp(name, "total") == 0) {
+        t1 = TIME_GET(save, total, 1) / 1000;
+        t2 = TIME_GET(save, total, 2) / 1000;
+        t3 = TIME_GET(save, total, 3) / 1000;
+    }
+
+    t0 = t1 + t2 + t3;
+
+    obj = qobject_from_jsonf("{ 'stage1': %" PRId64 ", "
+                               "'stage2': %" PRId64 ", "
+                               "'stage3': %" PRId64 ", "
+                               "'total' : %" PRId64 "  }", t1, t2, t3, t0);
+
+    qdict_put_obj(qdict, name, obj);
+}
+
+void do_info_migrate_times(Monitor *mon, QObject **ret_data)
+{
+    QDict *qdict;
+
+    if (time_measurement_type == TIME_MEASUREMENT_NONE) {
+        monitor_printf(mon, "No last operation has been found\n");
+        return;
+    }
+
+    qdict = qdict_new();
+    if (time_measurement_type == TIME_MEASUREMENT_SAVE) {
+        qdict_put(qdict, "type", qstring_from_str("save") );
+        qdict_put(qdict, "total-time", qint_from_int( time_save_measured / 1000 ) );
+        qdict_put(qdict, "wait-input", qint_from_int( TIME_GET(save, wait_input, 0) / 1000 ) );
+        qdict_put(qdict, "transferred-memory", qint_from_int( ram_bytes_transferred() ) );
+
+        migrate_times_put_status(qdict, "total");
+        if (TIME_GET(save, disk, 1) > 0)
+            migrate_times_put_status(qdict, "disk");
+        migrate_times_put_status(qdict, "ram");
+    }
+    else
+    if (time_measurement_type == TIME_MEASUREMENT_LOAD) {
+        qdict_put(qdict, "type", qstring_from_str("load") );
+        qdict_put(qdict, "total-time", qint_from_int( time_load_measured / 1000 ) );
+        qdict_put(qdict, "part1", qint_from_int( TIME_GET(load, part, 1) / 1000 ) );
+        qdict_put(qdict, "part2", qint_from_int( TIME_GET(load, part, 2) / 1000 ) );
+        qdict_put(qdict, "part3", qint_from_int( TIME_GET(load, part, 3) / 1000 ) );
+        qdict_put(qdict, "part4", qint_from_int( TIME_GET(load, part, 4) / 1000 ) );
+        qdict_put(qdict, "transferred-memory", qint_from_int( ram_bytes_transferred() ) );
+    }
+
+    *ret_data = QOBJECT(qdict);
+}
+