diff options
author | Anthony Liguori <aliguori@us.ibm.com> | 2012-10-22 13:26:23 -0500 |
---|---|---|
committer | Anthony Liguori <aliguori@us.ibm.com> | 2012-10-22 13:26:23 -0500 |
commit | f526f3c315d7c3ff7b7db39ae3a49f91d3a9986b (patch) | |
tree | dd24e41606c7bd9b53513571ed3b7864d71d237c | |
parent | d3e2efc5b540c4e99ed5bcc0db3b1158ef52af43 (diff) | |
parent | aa723c23147e93fef8475bd80fd29e633378c34d (diff) | |
download | qemu-f526f3c315d7c3ff7b7db39ae3a49f91d3a9986b.zip qemu-f526f3c315d7c3ff7b7db39ae3a49f91d3a9986b.tar.gz qemu-f526f3c315d7c3ff7b7db39ae3a49f91d3a9986b.tar.bz2 |
Merge remote-tracking branch 'quintela/migration-next-20121017' into staging
* quintela/migration-next-20121017: (41 commits)
cpus: create qemu_in_vcpu_thread()
savevm: make qemu_file_put_notify() return errors
savevm: un-export qemu_file_set_error()
block-migration: handle errors with the return codes correctly
block-migration: Switch meaning of return value
block-migration: make flush_blks() return errors
buffered_file: buffered_put_buffer() don't need to set last_error
savevm: Only qemu_fflush() can generate errors
savevm: make qemu_fill_buffer() be consistent
savevm: unexport qemu_ftell()
savevm: unfold qemu_fclose_internal()
savevm: make qemu_fflush() return an error code
savevm: Remove qemu_fseek()
virtio-net: use qemu_get_buffer() in a temp buffer
savevm: unexport qemu_fflush
migration: make migrate_fd_wait_for_unfreeze() return errors
buffered_file: make buffered_flush return the error code
buffered_file: callers of buffered_flush() already check for errors
buffered_file: We can access directly to bandwidth_limit
buffered_file: unfold migrate_fd_close
...
-rw-r--r-- | arch_init.c | 141 | ||||
-rw-r--r-- | block-migration.c | 56 | ||||
-rw-r--r-- | buffered_file.c | 129 | ||||
-rw-r--r-- | buffered_file.h | 12 | ||||
-rw-r--r-- | cpu-all.h | 2 | ||||
-rw-r--r-- | cpus.c | 9 | ||||
-rw-r--r-- | exec.c | 2 | ||||
-rw-r--r-- | hmp.c | 12 | ||||
-rw-r--r-- | hw/virtio-net.c | 4 | ||||
-rw-r--r-- | memory-internal.h | 10 | ||||
-rw-r--r-- | migration.c | 46 | ||||
-rw-r--r-- | migration.h | 10 | ||||
-rw-r--r-- | qapi-schema.json | 20 | ||||
-rw-r--r-- | qemu-file.h | 8 | ||||
-rw-r--r-- | qmp-commands.hx | 9 | ||||
-rw-r--r-- | savevm.c | 117 | ||||
-rw-r--r-- | trace-events | 4 |
17 files changed, 319 insertions, 272 deletions
diff --git a/arch_init.c b/arch_init.c index 9904f95..e6effe8 100644 --- a/arch_init.c +++ b/arch_init.c @@ -31,6 +31,8 @@ #include "config.h" #include "monitor.h" #include "sysemu.h" +#include "bitops.h" +#include "bitmap.h" #include "arch_init.h" #include "audio/audio.h" #include "hw/pc.h" @@ -45,6 +47,7 @@ #include "hw/pcspk.h" #include "qemu/page_cache.h" #include "qmp-commands.h" +#include "trace.h" #ifdef DEBUG_ARCH_INIT #define DPRINTF(fmt, ...) \ @@ -330,6 +333,78 @@ static int save_xbzrle_page(QEMUFile *f, uint8_t *current_data, static RAMBlock *last_block; static ram_addr_t last_offset; +static unsigned long *migration_bitmap; +static uint64_t migration_dirty_pages; + +static inline bool migration_bitmap_test_and_reset_dirty(MemoryRegion *mr, + ram_addr_t offset) +{ + bool ret; + int nr = (mr->ram_addr + offset) >> TARGET_PAGE_BITS; + + ret = test_and_clear_bit(nr, migration_bitmap); + + if (ret) { + migration_dirty_pages--; + } + return ret; +} + +static inline bool migration_bitmap_set_dirty(MemoryRegion *mr, + ram_addr_t offset) +{ + bool ret; + int nr = (mr->ram_addr + offset) >> TARGET_PAGE_BITS; + + ret = test_and_set_bit(nr, migration_bitmap); + + if (!ret) { + migration_dirty_pages++; + } + return ret; +} + +static void migration_bitmap_sync(void) +{ + RAMBlock *block; + ram_addr_t addr; + uint64_t num_dirty_pages_init = migration_dirty_pages; + MigrationState *s = migrate_get_current(); + static int64_t start_time; + static int64_t num_dirty_pages_period; + int64_t end_time; + + if (!start_time) { + start_time = qemu_get_clock_ms(rt_clock); + } + + trace_migration_bitmap_sync_start(); + memory_global_sync_dirty_bitmap(get_system_memory()); + + QLIST_FOREACH(block, &ram_list.blocks, next) { + for (addr = 0; addr < block->length; addr += TARGET_PAGE_SIZE) { + if (memory_region_get_dirty(block->mr, addr, TARGET_PAGE_SIZE, + DIRTY_MEMORY_MIGRATION)) { + migration_bitmap_set_dirty(block->mr, addr); + } + } + memory_region_reset_dirty(block->mr, 0, block->length, + DIRTY_MEMORY_MIGRATION); + } + trace_migration_bitmap_sync_end(migration_dirty_pages + - num_dirty_pages_init); + num_dirty_pages_period += migration_dirty_pages - num_dirty_pages_init; + end_time = qemu_get_clock_ms(rt_clock); + + /* more than 1 second = 1000 millisecons */ + if (end_time > start_time + 1000) { + s->dirty_pages_rate = num_dirty_pages_period * 1000 + / (end_time - start_time); + start_time = end_time; + num_dirty_pages_period = 0; + } +} + /* * ram_save_block: Writes a page of memory to the stream f @@ -352,14 +427,10 @@ static int ram_save_block(QEMUFile *f, bool last_stage) do { mr = block->mr; - if (memory_region_get_dirty(mr, offset, TARGET_PAGE_SIZE, - DIRTY_MEMORY_MIGRATION)) { + if (migration_bitmap_test_and_reset_dirty(mr, offset)) { uint8_t *p; int cont = (block == last_block) ? RAM_SAVE_FLAG_CONTINUE : 0; - memory_region_reset_dirty(mr, offset, TARGET_PAGE_SIZE, - DIRTY_MEMORY_MIGRATION); - p = memory_region_get_ram_ptr(mr) + offset; if (is_dup_page(p)) { @@ -409,7 +480,7 @@ static uint64_t bytes_transferred; static ram_addr_t ram_save_remaining(void) { - return ram_list.dirty_pages; + return migration_dirty_pages; } uint64_t ram_bytes_remaining(void) @@ -481,17 +552,27 @@ static void ram_migration_cancel(void *opaque) migration_end(); } + +static void reset_ram_globals(void) +{ + last_block = NULL; + last_offset = 0; + sort_ram_list(); +} + #define MAX_WAIT 50 /* ms, half buffered_file limit */ static int ram_save_setup(QEMUFile *f, void *opaque) { - ram_addr_t addr; RAMBlock *block; + int64_t ram_pages = last_ram_offset() >> TARGET_PAGE_BITS; + + migration_bitmap = bitmap_new(ram_pages); + bitmap_set(migration_bitmap, 1, ram_pages); + migration_dirty_pages = ram_pages; bytes_transferred = 0; - last_block = NULL; - last_offset = 0; - sort_ram_list(); + reset_ram_globals(); if (migrate_use_xbzrle()) { XBZRLE.cache = cache_init(migrate_xbzrle_cache_size() / @@ -506,17 +587,8 @@ static int ram_save_setup(QEMUFile *f, void *opaque) acct_clear(); } - /* Make sure all dirty bits are set */ - QLIST_FOREACH(block, &ram_list.blocks, next) { - for (addr = 0; addr < block->length; addr += TARGET_PAGE_SIZE) { - if (!memory_region_get_dirty(block->mr, addr, TARGET_PAGE_SIZE, - DIRTY_MEMORY_MIGRATION)) { - memory_region_set_dirty(block->mr, addr, TARGET_PAGE_SIZE); - } - } - } - memory_global_dirty_log_start(); + migration_bitmap_sync(); qemu_put_be64(f, ram_bytes_total() | RAM_SAVE_FLAG_MEM_SIZE); @@ -537,7 +609,8 @@ static int ram_save_iterate(QEMUFile *f, void *opaque) double bwidth = 0; int ret; int i; - uint64_t expected_time; + uint64_t expected_downtime; + MigrationState *s = migrate_get_current(); bytes_transferred_last = bytes_transferred; bwidth = qemu_get_clock_ns(rt_clock); @@ -576,31 +649,32 @@ static int ram_save_iterate(QEMUFile *f, void *opaque) bwidth = qemu_get_clock_ns(rt_clock) - bwidth; bwidth = (bytes_transferred - bytes_transferred_last) / bwidth; - /* if we haven't transferred anything this round, force expected_time to a - * a very high value, but without crashing */ + /* if we haven't transferred anything this round, force + * expected_downtime to a very high value, but without + * crashing */ if (bwidth == 0) { bwidth = 0.000001; } qemu_put_be64(f, RAM_SAVE_FLAG_EOS); - expected_time = ram_save_remaining() * TARGET_PAGE_SIZE / bwidth; + expected_downtime = ram_save_remaining() * TARGET_PAGE_SIZE / bwidth; + DPRINTF("ram_save_live: expected(%" PRIu64 ") <= max(" PRIu64 ")?\n", + expected_downtime, migrate_max_downtime()); - DPRINTF("ram_save_live: expected(%" PRIu64 ") <= max(%" PRIu64 ")?\n", - expected_time, migrate_max_downtime()); + if (expected_downtime <= migrate_max_downtime()) { + migration_bitmap_sync(); + expected_downtime = ram_save_remaining() * TARGET_PAGE_SIZE / bwidth; + s->expected_downtime = expected_downtime / 1000000; /* ns -> ms */ - if (expected_time <= migrate_max_downtime()) { - memory_global_sync_dirty_bitmap(get_system_memory()); - expected_time = ram_save_remaining() * TARGET_PAGE_SIZE / bwidth; - - return expected_time <= migrate_max_downtime(); + return expected_downtime <= migrate_max_downtime(); } return 0; } static int ram_save_complete(QEMUFile *f, void *opaque) { - memory_global_sync_dirty_bitmap(get_system_memory()); + migration_bitmap_sync(); /* try transferring iterative blocks of memory */ @@ -619,6 +693,9 @@ static int ram_save_complete(QEMUFile *f, void *opaque) qemu_put_be64(f, RAM_SAVE_FLAG_EOS); + g_free(migration_bitmap); + migration_bitmap = NULL; + return 0; } diff --git a/block-migration.c b/block-migration.c index ed93301..71b9601 100644 --- a/block-migration.c +++ b/block-migration.c @@ -423,20 +423,23 @@ static int mig_save_device_dirty(QEMUFile *f, BlkMigDevState *bmds, error: DPRINTF("Error reading sector %" PRId64 "\n", sector); - qemu_file_set_error(f, ret); g_free(blk->buf); g_free(blk); - return 0; + return ret; } +/* return value: + * 0: too much data for max_downtime + * 1: few enough data for max_downtime +*/ static int blk_mig_save_dirty_block(QEMUFile *f, int is_async) { BlkMigDevState *bmds; - int ret = 0; + int ret = 1; QSIMPLEQ_FOREACH(bmds, &block_mig_state.bmds_list, entry) { - if (mig_save_device_dirty(f, bmds, is_async) == 0) { - ret = 1; + ret = mig_save_device_dirty(f, bmds, is_async); + if (ret <= 0) { break; } } @@ -444,9 +447,10 @@ static int blk_mig_save_dirty_block(QEMUFile *f, int is_async) return ret; } -static void flush_blks(QEMUFile* f) +static int flush_blks(QEMUFile *f) { BlkMigBlock *blk; + int ret = 0; DPRINTF("%s Enter submitted %d read_done %d transferred %d\n", __FUNCTION__, block_mig_state.submitted, block_mig_state.read_done, @@ -457,7 +461,7 @@ static void flush_blks(QEMUFile* f) break; } if (blk->ret < 0) { - qemu_file_set_error(f, blk->ret); + ret = blk->ret; break; } blk_send(f, blk); @@ -474,6 +478,7 @@ static void flush_blks(QEMUFile* f) DPRINTF("%s Exit submitted %d read_done %d transferred %d\n", __FUNCTION__, block_mig_state.submitted, block_mig_state.read_done, block_mig_state.transferred); + return ret; } static int64_t get_remaining_dirty(void) @@ -555,9 +560,7 @@ static int block_save_setup(QEMUFile *f, void *opaque) /* start track dirty blocks */ set_dirty_tracking(1); - flush_blks(f); - - ret = qemu_file_get_error(f); + ret = flush_blks(f); if (ret) { blk_mig_cleanup(); return ret; @@ -577,9 +580,7 @@ static int block_save_iterate(QEMUFile *f, void *opaque) DPRINTF("Enter save live iterate submitted %d transferred %d\n", block_mig_state.submitted, block_mig_state.transferred); - flush_blks(f); - - ret = qemu_file_get_error(f); + ret = flush_blks(f); if (ret) { blk_mig_cleanup(); return ret; @@ -598,16 +599,19 @@ static int block_save_iterate(QEMUFile *f, void *opaque) block_mig_state.bulk_completed = 1; } } else { - if (blk_mig_save_dirty_block(f, 1) == 0) { + ret = blk_mig_save_dirty_block(f, 1); + if (ret != 0) { /* no more dirty blocks */ break; } } } + if (ret) { + blk_mig_cleanup(); + return ret; + } - flush_blks(f); - - ret = qemu_file_get_error(f); + ret = flush_blks(f); if (ret) { blk_mig_cleanup(); return ret; @@ -625,9 +629,7 @@ static int block_save_complete(QEMUFile *f, void *opaque) DPRINTF("Enter save live complete submitted %d transferred %d\n", block_mig_state.submitted, block_mig_state.transferred); - flush_blks(f); - - ret = qemu_file_get_error(f); + ret = flush_blks(f); if (ret) { blk_mig_cleanup(); return ret; @@ -639,18 +641,16 @@ static int block_save_complete(QEMUFile *f, void *opaque) all async read completed */ assert(block_mig_state.submitted == 0); - while (blk_mig_save_dirty_block(f, 0) != 0) { - /* Do nothing */ - } - blk_mig_cleanup(); - - /* report completion */ - qemu_put_be64(f, (100 << BDRV_SECTOR_BITS) | BLK_MIG_FLAG_PROGRESS); + do { + ret = blk_mig_save_dirty_block(f, 0); + } while (ret == 0); - ret = qemu_file_get_error(f); + blk_mig_cleanup(); if (ret) { return ret; } + /* report completion */ + qemu_put_be64(f, (100 << BDRV_SECTOR_BITS) | BLK_MIG_FLAG_PROGRESS); DPRINTF("Block migration completed\n"); diff --git a/buffered_file.c b/buffered_file.c index f170aa0..ed92df1 100644 --- a/buffered_file.c +++ b/buffered_file.c @@ -23,11 +23,7 @@ typedef struct QEMUFileBuffered { - BufferedPutFunc *put_buffer; - BufferedPutReadyFunc *put_ready; - BufferedWaitForUnfreezeFunc *wait_for_unfreeze; - BufferedCloseFunc *close; - void *opaque; + MigrationState *migration_state; QEMUFile *file; int freeze_output; size_t bytes_xfer; @@ -50,70 +46,60 @@ static void buffered_append(QEMUFileBuffered *s, const uint8_t *buf, size_t size) { if (size > (s->buffer_capacity - s->buffer_size)) { - void *tmp; - DPRINTF("increasing buffer capacity from %zu by %zu\n", s->buffer_capacity, size + 1024); s->buffer_capacity += size + 1024; - tmp = g_realloc(s->buffer, s->buffer_capacity); - if (tmp == NULL) { - fprintf(stderr, "qemu file buffer expansion failed\n"); - exit(1); - } - - s->buffer = tmp; + s->buffer = g_realloc(s->buffer, s->buffer_capacity); } memcpy(s->buffer + s->buffer_size, buf, size); s->buffer_size += size; } -static void buffered_flush(QEMUFileBuffered *s) +static ssize_t buffered_flush(QEMUFileBuffered *s) { size_t offset = 0; - int error; - - error = qemu_file_get_error(s->file); - if (error != 0) { - DPRINTF("flush when error, bailing: %s\n", strerror(-error)); - return; - } + ssize_t ret = 0; DPRINTF("flushing %zu byte(s) of data\n", s->buffer_size); - while (offset < s->buffer_size) { - ssize_t ret; + while (s->bytes_xfer < s->xfer_limit && offset < s->buffer_size) { - ret = s->put_buffer(s->opaque, s->buffer + offset, - s->buffer_size - offset); + ret = migrate_fd_put_buffer(s->migration_state, s->buffer + offset, + s->buffer_size - offset); if (ret == -EAGAIN) { DPRINTF("backend not ready, freezing\n"); + ret = 0; s->freeze_output = 1; break; } if (ret <= 0) { DPRINTF("error flushing data, %zd\n", ret); - qemu_file_set_error(s->file, ret); break; } else { DPRINTF("flushed %zd byte(s)\n", ret); offset += ret; + s->bytes_xfer += ret; } } DPRINTF("flushed %zu of %zu byte(s)\n", offset, s->buffer_size); memmove(s->buffer, s->buffer + offset, s->buffer_size - offset); s->buffer_size -= offset; + + if (ret < 0) { + return ret; + } + return offset; } static int buffered_put_buffer(void *opaque, const uint8_t *buf, int64_t pos, int size) { QEMUFileBuffered *s = opaque; - int offset = 0, error; - ssize_t ret; + ssize_t error; DPRINTF("putting %d bytes at %" PRId64 "\n", size, pos); @@ -126,65 +112,54 @@ static int buffered_put_buffer(void *opaque, const uint8_t *buf, int64_t pos, in DPRINTF("unfreezing output\n"); s->freeze_output = 0; - buffered_flush(s); - - while (!s->freeze_output && offset < size) { - if (s->bytes_xfer > s->xfer_limit) { - DPRINTF("transfer limit exceeded when putting\n"); - break; - } - - ret = s->put_buffer(s->opaque, buf + offset, size - offset); - if (ret == -EAGAIN) { - DPRINTF("backend not ready, freezing\n"); - s->freeze_output = 1; - break; - } - - if (ret <= 0) { - DPRINTF("error putting\n"); - qemu_file_set_error(s->file, ret); - offset = -EINVAL; - break; - } - - DPRINTF("put %zd byte(s)\n", ret); - offset += ret; - s->bytes_xfer += ret; + if (size > 0) { + DPRINTF("buffering %d bytes\n", size - offset); + buffered_append(s, buf, size); } - if (offset >= 0) { - DPRINTF("buffering %d bytes\n", size - offset); - buffered_append(s, buf + offset, size - offset); - offset = size; + error = buffered_flush(s); + if (error < 0) { + DPRINTF("buffered flush error. bailing: %s\n", strerror(-error)); + return error; } if (pos == 0 && size == 0) { DPRINTF("file is ready\n"); - if (s->bytes_xfer <= s->xfer_limit) { + if (!s->freeze_output && s->bytes_xfer < s->xfer_limit) { DPRINTF("notifying client\n"); - s->put_ready(s->opaque); + migrate_fd_put_ready(s->migration_state); } } - return offset; + return size; } static int buffered_close(void *opaque) { QEMUFileBuffered *s = opaque; - int ret; + ssize_t ret = 0; + int ret2; DPRINTF("closing\n"); + s->xfer_limit = INT_MAX; while (!qemu_file_get_error(s->file) && s->buffer_size) { - buffered_flush(s); - if (s->freeze_output) - s->wait_for_unfreeze(s->opaque); + ret = buffered_flush(s); + if (ret < 0) { + break; + } + if (s->freeze_output) { + ret = migrate_fd_wait_for_unfreeze(s->migration_state); + if (ret < 0) { + break; + } + } } - ret = s->close(s->opaque); - + ret2 = migrate_fd_close(s->migration_state); + if (ret >= 0) { + ret = ret2; + } qemu_del_timer(s->timer); qemu_free_timer(s->timer); g_free(s->buffer); @@ -256,29 +231,17 @@ static void buffered_rate_tick(void *opaque) s->bytes_xfer = 0; - buffered_flush(s); - - /* Add some checks around this */ - s->put_ready(s->opaque); + buffered_put_buffer(s, NULL, 0, 0); } -QEMUFile *qemu_fopen_ops_buffered(void *opaque, - size_t bytes_per_sec, - BufferedPutFunc *put_buffer, - BufferedPutReadyFunc *put_ready, - BufferedWaitForUnfreezeFunc *wait_for_unfreeze, - BufferedCloseFunc *close) +QEMUFile *qemu_fopen_ops_buffered(MigrationState *migration_state) { QEMUFileBuffered *s; s = g_malloc0(sizeof(*s)); - s->opaque = opaque; - s->xfer_limit = bytes_per_sec / 10; - s->put_buffer = put_buffer; - s->put_ready = put_ready; - s->wait_for_unfreeze = wait_for_unfreeze; - s->close = close; + s->migration_state = migration_state; + s->xfer_limit = migration_state->bandwidth_limit / 10; s->file = qemu_fopen_ops(s, buffered_put_buffer, NULL, buffered_close, buffered_rate_limit, diff --git a/buffered_file.h b/buffered_file.h index 98d358b..ef010fe 100644 --- a/buffered_file.h +++ b/buffered_file.h @@ -15,16 +15,8 @@ #define QEMU_BUFFERED_FILE_H #include "hw/hw.h" +#include "migration.h" -typedef ssize_t (BufferedPutFunc)(void *opaque, const void *data, size_t size); -typedef void (BufferedPutReadyFunc)(void *opaque); -typedef void (BufferedWaitForUnfreezeFunc)(void *opaque); -typedef int (BufferedCloseFunc)(void *opaque); - -QEMUFile *qemu_fopen_ops_buffered(void *opaque, size_t xfer_limit, - BufferedPutFunc *put_buffer, - BufferedPutReadyFunc *put_ready, - BufferedWaitForUnfreezeFunc *wait_for_unfreeze, - BufferedCloseFunc *close); +QEMUFile *qemu_fopen_ops_buffered(MigrationState *migration_state); #endif @@ -500,7 +500,6 @@ typedef struct RAMBlock { typedef struct RAMList { uint8_t *phys_dirty; QLIST_HEAD(, RAMBlock) blocks; - uint64_t dirty_pages; } RAMList; extern RAMList ram_list; @@ -518,6 +517,7 @@ extern int mem_prealloc; #define TLB_MMIO (1 << 5) void dump_exec_info(FILE *f, fprintf_function cpu_fprintf); +ram_addr_t last_ram_offset(void); #endif /* !CONFIG_USER_ONLY */ int cpu_memory_rw_debug(CPUArchState *env, target_ulong addr, @@ -898,6 +898,11 @@ int qemu_cpu_is_self(void *_env) return qemu_thread_is_self(cpu->thread); } +static bool qemu_in_vcpu_thread(void) +{ + return cpu_single_env && qemu_cpu_is_self(cpu_single_env); +} + void qemu_mutex_lock_iothread(void) { if (!tcg_enabled()) { @@ -943,7 +948,7 @@ void pause_all_vcpus(void) penv = penv->next_cpu; } - if (!qemu_thread_is_self(&io_thread)) { + if (qemu_in_vcpu_thread()) { cpu_stop_current(); if (!kvm_enabled()) { while (penv) { @@ -1060,7 +1065,7 @@ void cpu_stop_current(void) void vm_stop(RunState state) { - if (!qemu_thread_is_self(&io_thread)) { + if (qemu_in_vcpu_thread()) { qemu_system_vmstop_request(state); /* * FIXME: should not return to device code in case @@ -2449,7 +2449,7 @@ static ram_addr_t find_ram_offset(ram_addr_t size) return offset; } -static ram_addr_t last_ram_offset(void) +ram_addr_t last_ram_offset(void) { RAMBlock *block; ram_addr_t last = 0; @@ -152,6 +152,14 @@ void hmp_info_migrate(Monitor *mon) monitor_printf(mon, "Migration status: %s\n", info->status); monitor_printf(mon, "total time: %" PRIu64 " milliseconds\n", info->total_time); + if (info->has_expected_downtime) { + monitor_printf(mon, "expected downtime: %" PRIu64 " milliseconds\n", + info->expected_downtime); + } + if (info->has_downtime) { + monitor_printf(mon, "downtime: %" PRIu64 " milliseconds\n", + info->downtime); + } } if (info->has_ram) { @@ -167,6 +175,10 @@ void hmp_info_migrate(Monitor *mon) info->ram->normal); monitor_printf(mon, "normal bytes: %" PRIu64 " kbytes\n", info->ram->normal_bytes >> 10); + if (info->ram->dirty_pages_rate) { + monitor_printf(mon, "dirty pages rate: %" PRIu64 " pages\n", + info->ram->dirty_pages_rate); + } } if (info->has_disk) { diff --git a/hw/virtio-net.c b/hw/virtio-net.c index 8342391..50ba728 100644 --- a/hw/virtio-net.c +++ b/hw/virtio-net.c @@ -921,7 +921,9 @@ static int virtio_net_load(QEMUFile *f, void *opaque, int version_id) qemu_get_buffer(f, n->mac_table.macs, n->mac_table.in_use * ETH_ALEN); } else if (n->mac_table.in_use) { - qemu_fseek(f, n->mac_table.in_use * ETH_ALEN, SEEK_CUR); + uint8_t *buf = g_malloc0(n->mac_table.in_use); + qemu_get_buffer(f, buf, n->mac_table.in_use * ETH_ALEN); + g_free(buf); n->mac_table.multi_overflow = n->mac_table.uni_overflow = 1; n->mac_table.in_use = 0; } diff --git a/memory-internal.h b/memory-internal.h index 4d33cc9..b33a99d 100644 --- a/memory-internal.h +++ b/memory-internal.h @@ -90,11 +90,6 @@ static inline int cpu_physical_memory_get_dirty(ram_addr_t start, static inline int cpu_physical_memory_set_dirty_flags(ram_addr_t addr, int dirty_flags) { - if ((dirty_flags & MIGRATION_DIRTY_FLAG) && - !cpu_physical_memory_get_dirty(addr, TARGET_PAGE_SIZE, - MIGRATION_DIRTY_FLAG)) { - ram_list.dirty_pages++; - } return ram_list.phys_dirty[addr >> TARGET_PAGE_BITS] |= dirty_flags; } @@ -108,11 +103,6 @@ static inline int cpu_physical_memory_clear_dirty_flags(ram_addr_t addr, { int mask = ~dirty_flags; - if ((dirty_flags & MIGRATION_DIRTY_FLAG) && - cpu_physical_memory_get_dirty(addr, TARGET_PAGE_SIZE, - MIGRATION_DIRTY_FLAG)) { - ram_list.dirty_pages--; - } return ram_list.phys_dirty[addr >> TARGET_PAGE_BITS] &= mask; } diff --git a/migration.c b/migration.c index 22a05c4..62e0304 100644 --- a/migration.c +++ b/migration.c @@ -53,7 +53,7 @@ static NotifierList migration_state_notifiers = migrations at once. For now we don't need to add dynamic creation of migration */ -static MigrationState *migrate_get_current(void) +MigrationState *migrate_get_current(void) { static MigrationState current_migration = { .state = MIG_STATE_SETUP, @@ -169,6 +169,8 @@ MigrationInfo *qmp_query_migrate(Error **errp) info->has_total_time = true; info->total_time = qemu_get_clock_ms(rt_clock) - s->total_time; + info->has_expected_downtime = true; + info->expected_downtime = s->expected_downtime; info->has_ram = true; info->ram = g_malloc0(sizeof(*info->ram)); @@ -178,6 +180,8 @@ MigrationInfo *qmp_query_migrate(Error **errp) info->ram->duplicate = dup_mig_pages_transferred(); info->ram->normal = norm_mig_pages_transferred(); info->ram->normal_bytes = norm_mig_bytes_transferred(); + info->ram->dirty_pages_rate = s->dirty_pages_rate; + if (blk_mig_active()) { info->has_disk = true; @@ -195,6 +199,8 @@ MigrationInfo *qmp_query_migrate(Error **errp) info->has_status = true; info->status = g_strdup("completed"); info->total_time = s->total_time; + info->has_downtime = true; + info->downtime = s->downtime; info->has_ram = true; info->ram = g_malloc0(sizeof(*info->ram)); @@ -281,18 +287,18 @@ static void migrate_fd_completed(MigrationState *s) static void migrate_fd_put_notify(void *opaque) { MigrationState *s = opaque; + int ret; qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); - qemu_file_put_notify(s->file); - if (s->file && qemu_file_get_error(s->file)) { + ret = qemu_file_put_notify(s->file); + if (ret) { migrate_fd_error(s); } } -static ssize_t migrate_fd_put_buffer(void *opaque, const void *data, - size_t size) +ssize_t migrate_fd_put_buffer(MigrationState *s, const void *data, + size_t size) { - MigrationState *s = opaque; ssize_t ret; if (s->state != MIG_STATE_ACTIVE) { @@ -313,9 +319,8 @@ static ssize_t migrate_fd_put_buffer(void *opaque, const void *data, return ret; } -static void migrate_fd_put_ready(void *opaque) +void migrate_fd_put_ready(MigrationState *s) { - MigrationState *s = opaque; int ret; if (s->state != MIG_STATE_ACTIVE) { @@ -329,8 +334,10 @@ static void migrate_fd_put_ready(void *opaque) migrate_fd_error(s); } else if (ret == 1) { int old_vm_running = runstate_is_running(); + int64_t start_time, end_time; DPRINTF("done iterating\n"); + start_time = qemu_get_clock_ms(rt_clock); qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER); vm_stop_force_state(RUN_STATE_FINISH_MIGRATE); @@ -339,7 +346,9 @@ static void migrate_fd_put_ready(void *opaque) } else { migrate_fd_completed(s); } - s->total_time = qemu_get_clock_ms(rt_clock) - s->total_time; + end_time = qemu_get_clock_ms(rt_clock); + s->total_time = end_time - s->total_time; + s->downtime = end_time - start_time; if (s->state != MIG_STATE_COMPLETED) { if (old_vm_running) { vm_start(); @@ -362,14 +371,13 @@ static void migrate_fd_cancel(MigrationState *s) migrate_fd_cleanup(s); } -static void migrate_fd_wait_for_unfreeze(void *opaque) +int migrate_fd_wait_for_unfreeze(MigrationState *s) { - MigrationState *s = opaque; int ret; DPRINTF("wait for unfreeze\n"); if (s->state != MIG_STATE_ACTIVE) - return; + return -EINVAL; do { fd_set wfds; @@ -381,14 +389,13 @@ static void migrate_fd_wait_for_unfreeze(void *opaque) } while (ret == -1 && (s->get_error(s)) == EINTR); if (ret == -1) { - qemu_file_set_error(s->file, -s->get_error(s)); + return -s->get_error(s); } + return 0; } -static int migrate_fd_close(void *opaque) +int migrate_fd_close(MigrationState *s) { - MigrationState *s = opaque; - qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); return s->close(s); } @@ -424,12 +431,7 @@ void migrate_fd_connect(MigrationState *s) int ret; s->state = MIG_STATE_ACTIVE; - s->file = qemu_fopen_ops_buffered(s, - s->bandwidth_limit, - migrate_fd_put_buffer, - migrate_fd_put_ready, - migrate_fd_wait_for_unfreeze, - migrate_fd_close); + s->file = qemu_fopen_ops_buffered(s); DPRINTF("beginning savevm\n"); ret = qemu_savevm_state_begin(s->file, &s->params); diff --git a/migration.h b/migration.h index a9852fc..1c3e9b7 100644 --- a/migration.h +++ b/migration.h @@ -40,6 +40,9 @@ struct MigrationState void *opaque; MigrationParams params; int64_t total_time; + int64_t downtime; + int64_t expected_downtime; + int64_t dirty_pages_rate; bool enabled_capabilities[MIGRATION_CAPABILITY_MAX]; int64_t xbzrle_cache_size; }; @@ -75,11 +78,18 @@ void migrate_fd_error(MigrationState *s); void migrate_fd_connect(MigrationState *s); +ssize_t migrate_fd_put_buffer(MigrationState *s, const void *data, + size_t size); +void migrate_fd_put_ready(MigrationState *s); +int migrate_fd_wait_for_unfreeze(MigrationState *s); +int migrate_fd_close(MigrationState *s); + void add_migration_state_change_notifier(Notifier *notify); void remove_migration_state_change_notifier(Notifier *notify); bool migration_is_active(MigrationState *); bool migration_has_finished(MigrationState *); bool migration_has_failed(MigrationState *); +MigrationState *migrate_get_current(void); uint64_t ram_bytes_remaining(void); uint64_t ram_bytes_transferred(void); diff --git a/qapi-schema.json b/qapi-schema.json index f9dbdae..c615ee2 100644 --- a/qapi-schema.json +++ b/qapi-schema.json @@ -383,13 +383,17 @@ # # @normal : number of normal pages (since 1.2) # -# @normal-bytes : number of normal bytes sent (since 1.2) +# @normal-bytes: number of normal bytes sent (since 1.2) +# +# @dirty-pages-rate: number of pages dirtied by second by the +# guest (since 1.3) # # Since: 0.14.0 ## { 'type': 'MigrationStats', 'data': {'transferred': 'int', 'remaining': 'int', 'total': 'int' , - 'duplicate': 'int', 'normal': 'int', 'normal-bytes': 'int' } } + 'duplicate': 'int', 'normal': 'int', 'normal-bytes': 'int', + 'dirty-pages-rate' : 'int' } } ## # @XBZRLECacheStats @@ -438,13 +442,23 @@ # If migration has ended, it returns the total migration # time. (since 1.2) # +# @downtime: #optional only present when migration finishes correctly +# total downtime in milliseconds for the guest. +# (since 1.3) +# +# @expected-downtime: #optional only present while migration is active +# expected downtime in milliseconds for the guest in last walk +# of the dirty bitmap. (since 1.3) +# # Since: 0.14.0 ## { 'type': 'MigrationInfo', 'data': {'*status': 'str', '*ram': 'MigrationStats', '*disk': 'MigrationStats', '*xbzrle-cache': 'XBZRLECacheStats', - '*total-time': 'int'} } + '*total-time': 'int', + '*expected-downtime': 'int', + '*downtime': 'int'} } ## # @query-migrate diff --git a/qemu-file.h b/qemu-file.h index 31b83f6..9c8985b 100644 --- a/qemu-file.h +++ b/qemu-file.h @@ -71,7 +71,6 @@ QEMUFile *qemu_fopen_socket(int fd); QEMUFile *qemu_popen(FILE *popen_file, const char *mode); QEMUFile *qemu_popen_cmd(const char *command, const char *mode); int qemu_stdio_fd(QEMUFile *f); -void qemu_fflush(QEMUFile *f); int qemu_fclose(QEMUFile *f); void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, int size); void qemu_put_byte(QEMUFile *f, int v); @@ -104,12 +103,11 @@ int qemu_file_rate_limit(QEMUFile *f); int64_t qemu_file_set_rate_limit(QEMUFile *f, int64_t new_rate); int64_t qemu_file_get_rate_limit(QEMUFile *f); int qemu_file_get_error(QEMUFile *f); -void qemu_file_set_error(QEMUFile *f, int error); /* Try to send any outstanding data. This function is useful when output is * halted due to rate limiting or EAGAIN errors occur as it can be used to * resume output. */ -void qemu_file_put_notify(QEMUFile *f); +int qemu_file_put_notify(QEMUFile *f); static inline void qemu_put_be64s(QEMUFile *f, const uint64_t *pv) { @@ -231,8 +229,4 @@ static inline void qemu_get_sbe64s(QEMUFile *f, int64_t *pv) { qemu_get_be64s(f, (uint64_t *)pv); } - -int64_t qemu_ftell(QEMUFile *f); -int64_t qemu_fseek(QEMUFile *f, int64_t pos, int whence); - #endif diff --git a/qmp-commands.hx b/qmp-commands.hx index 2f8477e..5ba8c48 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -2304,6 +2304,11 @@ The main json-object contains the following: - "total-time": total amount of ms since migration started. If migration has ended, it returns the total migration time (json-int) +- "downtime": only present when migration has finished correctly + total amount in ms for downtime that happened (json-int) +- "expected-downtime": only present while migration is active + total amount in ms for downtime that was calculated on + the last bitmap round (json-int) - "ram": only present if "status" is "active", it is a json-object with the following RAM information (in bytes): - "transferred": amount transferred (json-int) @@ -2341,6 +2346,7 @@ Examples: "remaining":123, "total":246, "total-time":12345, + "downtime":12345, "duplicate":123, "normal":123, "normal-bytes":123456 @@ -2364,6 +2370,7 @@ Examples: "remaining":123, "total":246, "total-time":12345, + "expected-downtime":12345, "duplicate":123, "normal":123, "normal-bytes":123456 @@ -2382,6 +2389,7 @@ Examples: "remaining":1053304, "transferred":3720, "total-time":12345, + "expected-downtime":12345, "duplicate":123, "normal":123, "normal-bytes":123456 @@ -2406,6 +2414,7 @@ Examples: "remaining":1053304, "transferred":3720, "total-time":12345, + "expected-downtime":12345, "duplicate":10, "normal":3333, "normal-bytes":3412992 @@ -440,42 +440,29 @@ int qemu_file_get_error(QEMUFile *f) return f->last_error; } -void qemu_file_set_error(QEMUFile *f, int ret) +static void qemu_file_set_error(QEMUFile *f, int ret) { f->last_error = ret; } -/** Sets last_error conditionally - * - * Sets last_error only if ret is negative _and_ no error - * was set before. - */ -static void qemu_file_set_if_error(QEMUFile *f, int ret) -{ - if (ret < 0 && !f->last_error) { - qemu_file_set_error(f, ret); - } -} - /** Flushes QEMUFile buffer * - * In case of error, last_error is set. */ -void qemu_fflush(QEMUFile *f) +static int qemu_fflush(QEMUFile *f) { + int ret = 0; + if (!f->put_buffer) - return; + return 0; if (f->is_write && f->buf_index > 0) { - int len; - - len = f->put_buffer(f->opaque, f->buf, f->buf_offset, f->buf_index); - if (len > 0) + ret = f->put_buffer(f->opaque, f->buf, f->buf_offset, f->buf_index); + if (ret >= 0) { f->buf_offset += f->buf_index; - else - qemu_file_set_error(f, -EINVAL); + } f->buf_index = 0; } + return ret; } static void qemu_fill_buffer(QEMUFile *f) @@ -502,27 +489,11 @@ static void qemu_fill_buffer(QEMUFile *f) f->buf_size += len; f->buf_offset += len; } else if (len == 0) { - f->last_error = -EIO; + qemu_file_set_error(f, -EIO); } else if (len != -EAGAIN) qemu_file_set_error(f, len); } -/** Calls close function and set last_error if needed - * - * Internal function. qemu_fflush() must be called before this. - * - * Returns f->close() return value, or 0 if close function is not set. - */ -static int qemu_fclose_internal(QEMUFile *f) -{ - int ret = 0; - if (f->close) { - ret = f->close(f->opaque); - qemu_file_set_if_error(f, ret); - } - return ret; -} - /** Closes the file * * Returns negative error value if any error happened on previous operations or @@ -534,8 +505,14 @@ static int qemu_fclose_internal(QEMUFile *f) int qemu_fclose(QEMUFile *f) { int ret; - qemu_fflush(f); - ret = qemu_fclose_internal(f); + ret = qemu_fflush(f); + + if (f->close) { + int ret2 = f->close(f->opaque); + if (ret >= 0) { + ret = ret2; + } + } /* If any error was spotted before closing, we should report it * instead of the close() return value. */ @@ -546,22 +523,26 @@ int qemu_fclose(QEMUFile *f) return ret; } -void qemu_file_put_notify(QEMUFile *f) +int qemu_file_put_notify(QEMUFile *f) { - f->put_buffer(f->opaque, NULL, 0, 0); + return f->put_buffer(f->opaque, NULL, 0, 0); } void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, int size) { int l; - if (!f->last_error && f->is_write == 0 && f->buf_index > 0) { + if (f->last_error) { + return; + } + + if (f->is_write == 0 && f->buf_index > 0) { fprintf(stderr, "Attempted to write to buffer while read buffer is not empty\n"); abort(); } - while (!f->last_error && size > 0) { + while (size > 0) { l = IO_BUF_SIZE - f->buf_index; if (l > size) l = size; @@ -570,14 +551,23 @@ void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, int size) f->buf_index += l; buf += l; size -= l; - if (f->buf_index >= IO_BUF_SIZE) - qemu_fflush(f); + if (f->buf_index >= IO_BUF_SIZE) { + int ret = qemu_fflush(f); + if (ret < 0) { + qemu_file_set_error(f, ret); + break; + } + } } } void qemu_put_byte(QEMUFile *f, int v) { - if (!f->last_error && f->is_write == 0 && f->buf_index > 0) { + if (f->last_error) { + return; + } + + if (f->is_write == 0 && f->buf_index > 0) { fprintf(stderr, "Attempted to write to buffer while read buffer is not empty\n"); abort(); @@ -585,8 +575,12 @@ void qemu_put_byte(QEMUFile *f, int v) f->buf[f->buf_index++] = v; f->is_write = 1; - if (f->buf_index >= IO_BUF_SIZE) - qemu_fflush(f); + if (f->buf_index >= IO_BUF_SIZE) { + int ret = qemu_fflush(f); + if (ret < 0) { + qemu_file_set_error(f, ret); + } + } } static void qemu_file_skip(QEMUFile *f, int size) @@ -671,32 +665,11 @@ int qemu_get_byte(QEMUFile *f) return result; } -int64_t qemu_ftell(QEMUFile *f) +static int64_t qemu_ftell(QEMUFile *f) { return f->buf_offset - f->buf_size + f->buf_index; } -int64_t qemu_fseek(QEMUFile *f, int64_t pos, int whence) -{ - if (whence == SEEK_SET) { - /* nothing to do */ - } else if (whence == SEEK_CUR) { - pos += qemu_ftell(f); - } else { - /* SEEK_END not supported */ - return -1; - } - if (f->put_buffer) { - qemu_fflush(f); - f->buf_offset = pos; - } else { - f->buf_offset = pos; - f->buf_index = 0; - f->buf_size = 0; - } - return pos; -} - int qemu_file_rate_limit(QEMUFile *f) { if (f->rate_limit) diff --git a/trace-events b/trace-events index 42b66f1..e2d4580 100644 --- a/trace-events +++ b/trace-events @@ -921,6 +921,10 @@ ppm_save(const char *filename, void *display_surface) "%s surface=%p" savevm_section_start(void) "" savevm_section_end(unsigned int section_id) "section_id %u" +# arch_init.c +migration_bitmap_sync_start(void) "" +migration_bitmap_sync_end(uint64_t dirty_pages) "dirty_pages %" PRIu64"" + # hw/qxl.c disable qxl_interface_set_mm_time(int qid, uint32_t mm_time) "%d %d" disable qxl_io_write_vga(int qid, const char *mode, uint32_t addr, uint32_t val) "%d %s addr=%u val=%u" |