mirror of
https://github.com/xemu-project/xemu.git
synced 2025-02-17 10:38:52 +00:00
Merge remote-tracking branch 'quintela/migration-next-20120808' into staging
* quintela/migration-next-20120808: Restart optimization on stage3 update version Add XBZRLE statistics Add migration accounting for normal and duplicate pages Change total_time to total-time in MigrationStats Add migrate_set_cache_size command Add XBZRLE to ram_save_block and ram_save_live Add xbzrle_encode_buffer and xbzrle_decode_buffer functions Add uleb encoding/decoding functions Add cache handling functions Add XBZRLE documentation Add migrate-set-capabilities Add migration capabilities
This commit is contained in:
commit
ac839ccd8c
@ -77,6 +77,7 @@ common-obj-y += qemu-char.o #aio.o
|
||||
common-obj-y += block-migration.o iohandler.o
|
||||
common-obj-y += pflib.o
|
||||
common-obj-y += bitmap.o bitops.o
|
||||
common-obj-y += page_cache.o
|
||||
|
||||
common-obj-$(CONFIG_POSIX) += migration-exec.o migration-unix.o migration-fd.o
|
||||
common-obj-$(CONFIG_WIN32) += version.o
|
||||
|
246
arch_init.c
246
arch_init.c
@ -43,6 +43,7 @@
|
||||
#include "hw/smbios.h"
|
||||
#include "exec-memory.h"
|
||||
#include "hw/pcspk.h"
|
||||
#include "qemu/page_cache.h"
|
||||
|
||||
#ifdef DEBUG_ARCH_INIT
|
||||
#define DPRINTF(fmt, ...) \
|
||||
@ -106,6 +107,7 @@ const uint32_t arch_type = QEMU_ARCH;
|
||||
#define RAM_SAVE_FLAG_PAGE 0x08
|
||||
#define RAM_SAVE_FLAG_EOS 0x10
|
||||
#define RAM_SAVE_FLAG_CONTINUE 0x20
|
||||
#define RAM_SAVE_FLAG_XBZRLE 0x40
|
||||
|
||||
#ifdef __ALTIVEC__
|
||||
#include <altivec.h>
|
||||
@ -173,6 +175,92 @@ static int is_dup_page(uint8_t *page)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* struct contains XBZRLE cache and a static page
|
||||
used by the compression */
|
||||
static struct {
|
||||
/* buffer used for XBZRLE encoding */
|
||||
uint8_t *encoded_buf;
|
||||
/* buffer for storing page content */
|
||||
uint8_t *current_buf;
|
||||
/* buffer used for XBZRLE decoding */
|
||||
uint8_t *decoded_buf;
|
||||
/* Cache for XBZRLE */
|
||||
PageCache *cache;
|
||||
} XBZRLE = {
|
||||
.encoded_buf = NULL,
|
||||
.current_buf = NULL,
|
||||
.decoded_buf = NULL,
|
||||
.cache = NULL,
|
||||
};
|
||||
|
||||
|
||||
int64_t xbzrle_cache_resize(int64_t new_size)
|
||||
{
|
||||
if (XBZRLE.cache != NULL) {
|
||||
return cache_resize(XBZRLE.cache, new_size / TARGET_PAGE_SIZE) *
|
||||
TARGET_PAGE_SIZE;
|
||||
}
|
||||
return pow2floor(new_size);
|
||||
}
|
||||
|
||||
/* accounting for migration statistics */
|
||||
typedef struct AccountingInfo {
|
||||
uint64_t dup_pages;
|
||||
uint64_t norm_pages;
|
||||
uint64_t iterations;
|
||||
uint64_t xbzrle_bytes;
|
||||
uint64_t xbzrle_pages;
|
||||
uint64_t xbzrle_cache_miss;
|
||||
uint64_t xbzrle_overflows;
|
||||
} AccountingInfo;
|
||||
|
||||
static AccountingInfo acct_info;
|
||||
|
||||
static void acct_clear(void)
|
||||
{
|
||||
memset(&acct_info, 0, sizeof(acct_info));
|
||||
}
|
||||
|
||||
uint64_t dup_mig_bytes_transferred(void)
|
||||
{
|
||||
return acct_info.dup_pages * TARGET_PAGE_SIZE;
|
||||
}
|
||||
|
||||
uint64_t dup_mig_pages_transferred(void)
|
||||
{
|
||||
return acct_info.dup_pages;
|
||||
}
|
||||
|
||||
uint64_t norm_mig_bytes_transferred(void)
|
||||
{
|
||||
return acct_info.norm_pages * TARGET_PAGE_SIZE;
|
||||
}
|
||||
|
||||
uint64_t norm_mig_pages_transferred(void)
|
||||
{
|
||||
return acct_info.norm_pages;
|
||||
}
|
||||
|
||||
uint64_t xbzrle_mig_bytes_transferred(void)
|
||||
{
|
||||
return acct_info.xbzrle_bytes;
|
||||
}
|
||||
|
||||
uint64_t xbzrle_mig_pages_transferred(void)
|
||||
{
|
||||
return acct_info.xbzrle_pages;
|
||||
}
|
||||
|
||||
uint64_t xbzrle_mig_pages_cache_miss(void)
|
||||
{
|
||||
return acct_info.xbzrle_cache_miss;
|
||||
}
|
||||
|
||||
uint64_t xbzrle_mig_pages_overflow(void)
|
||||
{
|
||||
return acct_info.xbzrle_overflows;
|
||||
}
|
||||
|
||||
static void save_block_hdr(QEMUFile *f, RAMBlock *block, ram_addr_t offset,
|
||||
int cont, int flag)
|
||||
{
|
||||
@ -185,6 +273,61 @@ static void save_block_hdr(QEMUFile *f, RAMBlock *block, ram_addr_t offset,
|
||||
|
||||
}
|
||||
|
||||
#define ENCODING_FLAG_XBZRLE 0x1
|
||||
|
||||
static int save_xbzrle_page(QEMUFile *f, uint8_t *current_data,
|
||||
ram_addr_t current_addr, RAMBlock *block,
|
||||
ram_addr_t offset, int cont, bool last_stage)
|
||||
{
|
||||
int encoded_len = 0, bytes_sent = -1;
|
||||
uint8_t *prev_cached_page;
|
||||
|
||||
if (!cache_is_cached(XBZRLE.cache, current_addr)) {
|
||||
if (!last_stage) {
|
||||
cache_insert(XBZRLE.cache, current_addr,
|
||||
g_memdup(current_data, TARGET_PAGE_SIZE));
|
||||
}
|
||||
acct_info.xbzrle_cache_miss++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
prev_cached_page = get_cached_data(XBZRLE.cache, current_addr);
|
||||
|
||||
/* save current buffer into memory */
|
||||
memcpy(XBZRLE.current_buf, current_data, TARGET_PAGE_SIZE);
|
||||
|
||||
/* XBZRLE encoding (if there is no overflow) */
|
||||
encoded_len = xbzrle_encode_buffer(prev_cached_page, XBZRLE.current_buf,
|
||||
TARGET_PAGE_SIZE, XBZRLE.encoded_buf,
|
||||
TARGET_PAGE_SIZE);
|
||||
if (encoded_len == 0) {
|
||||
DPRINTF("Skipping unmodified page\n");
|
||||
return 0;
|
||||
} else if (encoded_len == -1) {
|
||||
DPRINTF("Overflow\n");
|
||||
acct_info.xbzrle_overflows++;
|
||||
/* update data in the cache */
|
||||
memcpy(prev_cached_page, current_data, TARGET_PAGE_SIZE);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* we need to update the data in the cache, in order to get the same data */
|
||||
if (!last_stage) {
|
||||
memcpy(prev_cached_page, XBZRLE.current_buf, TARGET_PAGE_SIZE);
|
||||
}
|
||||
|
||||
/* Send XBZRLE based compressed page */
|
||||
save_block_hdr(f, block, offset, cont, RAM_SAVE_FLAG_XBZRLE);
|
||||
qemu_put_byte(f, ENCODING_FLAG_XBZRLE);
|
||||
qemu_put_be16(f, encoded_len);
|
||||
qemu_put_buffer(f, XBZRLE.encoded_buf, encoded_len);
|
||||
bytes_sent = encoded_len + 1 + 2;
|
||||
acct_info.xbzrle_pages++;
|
||||
acct_info.xbzrle_bytes += bytes_sent;
|
||||
|
||||
return bytes_sent;
|
||||
}
|
||||
|
||||
static RAMBlock *last_block;
|
||||
static ram_addr_t last_offset;
|
||||
|
||||
@ -196,12 +339,13 @@ static ram_addr_t last_offset;
|
||||
* n: the amount of bytes written in other case
|
||||
*/
|
||||
|
||||
static int ram_save_block(QEMUFile *f)
|
||||
static int ram_save_block(QEMUFile *f, bool last_stage)
|
||||
{
|
||||
RAMBlock *block = last_block;
|
||||
ram_addr_t offset = last_offset;
|
||||
int bytes_sent = -1;
|
||||
MemoryRegion *mr;
|
||||
ram_addr_t current_addr;
|
||||
|
||||
if (!block)
|
||||
block = QLIST_FIRST(&ram_list.blocks);
|
||||
@ -219,16 +363,31 @@ static int ram_save_block(QEMUFile *f)
|
||||
p = memory_region_get_ram_ptr(mr) + offset;
|
||||
|
||||
if (is_dup_page(p)) {
|
||||
acct_info.dup_pages++;
|
||||
save_block_hdr(f, block, offset, cont, RAM_SAVE_FLAG_COMPRESS);
|
||||
qemu_put_byte(f, *p);
|
||||
bytes_sent = 1;
|
||||
} else {
|
||||
} else if (migrate_use_xbzrle()) {
|
||||
current_addr = block->offset + offset;
|
||||
bytes_sent = save_xbzrle_page(f, p, current_addr, block,
|
||||
offset, cont, last_stage);
|
||||
if (!last_stage) {
|
||||
p = get_cached_data(XBZRLE.cache, current_addr);
|
||||
}
|
||||
}
|
||||
|
||||
/* either we didn't send yet (we may have had XBZRLE overflow) */
|
||||
if (bytes_sent == -1) {
|
||||
save_block_hdr(f, block, offset, cont, RAM_SAVE_FLAG_PAGE);
|
||||
qemu_put_buffer(f, p, TARGET_PAGE_SIZE);
|
||||
bytes_sent = TARGET_PAGE_SIZE;
|
||||
acct_info.norm_pages++;
|
||||
}
|
||||
|
||||
break;
|
||||
/* if page is unmodified, continue to the next */
|
||||
if (bytes_sent != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
offset += TARGET_PAGE_SIZE;
|
||||
@ -306,6 +465,15 @@ static void sort_ram_list(void)
|
||||
static void migration_end(void)
|
||||
{
|
||||
memory_global_dirty_log_stop();
|
||||
|
||||
if (migrate_use_xbzrle()) {
|
||||
cache_fini(XBZRLE.cache);
|
||||
g_free(XBZRLE.cache);
|
||||
g_free(XBZRLE.encoded_buf);
|
||||
g_free(XBZRLE.current_buf);
|
||||
g_free(XBZRLE.decoded_buf);
|
||||
XBZRLE.cache = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void ram_migration_cancel(void *opaque)
|
||||
@ -325,6 +493,19 @@ static int ram_save_setup(QEMUFile *f, void *opaque)
|
||||
last_offset = 0;
|
||||
sort_ram_list();
|
||||
|
||||
if (migrate_use_xbzrle()) {
|
||||
XBZRLE.cache = cache_init(migrate_xbzrle_cache_size() /
|
||||
TARGET_PAGE_SIZE,
|
||||
TARGET_PAGE_SIZE);
|
||||
if (!XBZRLE.cache) {
|
||||
DPRINTF("Error creating cache\n");
|
||||
return -1;
|
||||
}
|
||||
XBZRLE.encoded_buf = g_malloc0(TARGET_PAGE_SIZE);
|
||||
XBZRLE.current_buf = g_malloc(TARGET_PAGE_SIZE);
|
||||
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) {
|
||||
@ -365,12 +546,13 @@ static int ram_save_iterate(QEMUFile *f, void *opaque)
|
||||
while ((ret = qemu_file_rate_limit(f)) == 0) {
|
||||
int bytes_sent;
|
||||
|
||||
bytes_sent = ram_save_block(f);
|
||||
bytes_sent = ram_save_block(f, false);
|
||||
/* no more blocks to sent */
|
||||
if (bytes_sent < 0) {
|
||||
break;
|
||||
}
|
||||
bytes_transferred += bytes_sent;
|
||||
acct_info.iterations++;
|
||||
/* we want to check in the 1st loop, just in case it was the 1st time
|
||||
and we had to sync the dirty bitmap.
|
||||
qemu_get_clock_ns() is a bit expensive, so we only check each some
|
||||
@ -426,7 +608,7 @@ static int ram_save_complete(QEMUFile *f, void *opaque)
|
||||
while (true) {
|
||||
int bytes_sent;
|
||||
|
||||
bytes_sent = ram_save_block(f);
|
||||
bytes_sent = ram_save_block(f, true);
|
||||
/* no more blocks to sent */
|
||||
if (bytes_sent < 0) {
|
||||
break;
|
||||
@ -440,6 +622,47 @@ static int ram_save_complete(QEMUFile *f, void *opaque)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int load_xbzrle(QEMUFile *f, ram_addr_t addr, void *host)
|
||||
{
|
||||
int ret, rc = 0;
|
||||
unsigned int xh_len;
|
||||
int xh_flags;
|
||||
|
||||
if (!XBZRLE.decoded_buf) {
|
||||
XBZRLE.decoded_buf = g_malloc(TARGET_PAGE_SIZE);
|
||||
}
|
||||
|
||||
/* extract RLE header */
|
||||
xh_flags = qemu_get_byte(f);
|
||||
xh_len = qemu_get_be16(f);
|
||||
|
||||
if (xh_flags != ENCODING_FLAG_XBZRLE) {
|
||||
fprintf(stderr, "Failed to load XBZRLE page - wrong compression!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (xh_len > TARGET_PAGE_SIZE) {
|
||||
fprintf(stderr, "Failed to load XBZRLE page - len overflow!\n");
|
||||
return -1;
|
||||
}
|
||||
/* load data and decode */
|
||||
qemu_get_buffer(f, XBZRLE.decoded_buf, xh_len);
|
||||
|
||||
/* decode RLE */
|
||||
ret = xbzrle_decode_buffer(XBZRLE.decoded_buf, xh_len, host,
|
||||
TARGET_PAGE_SIZE);
|
||||
if (ret == -1) {
|
||||
fprintf(stderr, "Failed to load XBZRLE page - decode error!\n");
|
||||
rc = -1;
|
||||
} else if (ret > TARGET_PAGE_SIZE) {
|
||||
fprintf(stderr, "Failed to load XBZRLE page - size %d exceeds %d!\n",
|
||||
ret, TARGET_PAGE_SIZE);
|
||||
abort();
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static inline void *host_from_stream_offset(QEMUFile *f,
|
||||
ram_addr_t offset,
|
||||
int flags)
|
||||
@ -553,6 +776,19 @@ static int ram_load(QEMUFile *f, void *opaque, int version_id)
|
||||
}
|
||||
|
||||
qemu_get_buffer(f, host, TARGET_PAGE_SIZE);
|
||||
} else if (flags & RAM_SAVE_FLAG_XBZRLE) {
|
||||
if (!migrate_use_xbzrle()) {
|
||||
return -EINVAL;
|
||||
}
|
||||
void *host = host_from_stream_offset(f, addr, flags);
|
||||
if (!host) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (load_xbzrle(f, addr, host) < 0) {
|
||||
ret = -EINVAL;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
error = qemu_file_get_error(f);
|
||||
if (error) {
|
||||
|
42
cutils.c
42
cutils.c
@ -382,3 +382,45 @@ int qemu_parse_fd(const char *param)
|
||||
}
|
||||
return fd;
|
||||
}
|
||||
|
||||
/* round down to the nearest power of 2*/
|
||||
int64_t pow2floor(int64_t value)
|
||||
{
|
||||
if (!is_power_of_2(value)) {
|
||||
value = 0x8000000000000000ULL >> clz64(value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementation of ULEB128 (http://en.wikipedia.org/wiki/LEB128)
|
||||
* Input is limited to 14-bit numbers
|
||||
*/
|
||||
int uleb128_encode_small(uint8_t *out, uint32_t n)
|
||||
{
|
||||
g_assert(n <= 0x3fff);
|
||||
if (n < 0x80) {
|
||||
*out++ = n;
|
||||
return 1;
|
||||
} else {
|
||||
*out++ = (n & 0x7f) | 0x80;
|
||||
*out++ = n >> 7;
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
int uleb128_decode_small(const uint8_t *in, uint32_t *n)
|
||||
{
|
||||
if (!(*in & 0x80)) {
|
||||
*n = *in++;
|
||||
return 1;
|
||||
} else {
|
||||
*n = *in++ & 0x7f;
|
||||
/* we exceed 14 bit number */
|
||||
if (*in & 0x80) {
|
||||
return -1;
|
||||
}
|
||||
*n |= *in++ << 7;
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
128
docs/xbzrle.txt
Normal file
128
docs/xbzrle.txt
Normal file
@ -0,0 +1,128 @@
|
||||
XBZRLE (Xor Based Zero Run Length Encoding)
|
||||
===========================================
|
||||
|
||||
Using XBZRLE (Xor Based Zero Run Length Encoding) allows for the reduction
|
||||
of VM downtime and the total live-migration time of Virtual machines.
|
||||
It is particularly useful for virtual machines running memory write intensive
|
||||
workloads that are typical of large enterprise applications such as SAP ERP
|
||||
Systems, and generally speaking for any application that uses a sparse memory
|
||||
update pattern.
|
||||
|
||||
Instead of sending the changed guest memory page this solution will send a
|
||||
compressed version of the updates, thus reducing the amount of data sent during
|
||||
live migration.
|
||||
In order to be able to calculate the update, the previous memory pages need to
|
||||
be stored on the source. Those pages are stored in a dedicated cache
|
||||
(hash table) and are accessed by their address.
|
||||
The larger the cache size the better the chances are that the page has already
|
||||
been stored in the cache.
|
||||
A small cache size will result in high cache miss rate.
|
||||
Cache size can be changed before and during migration.
|
||||
|
||||
Format
|
||||
=======
|
||||
|
||||
The compression format performs a XOR between the previous and current content
|
||||
of the page, where zero represents an unchanged value.
|
||||
The page data delta is represented by zero and non zero runs.
|
||||
A zero run is represented by its length (in bytes).
|
||||
A non zero run is represented by its length (in bytes) and the new data.
|
||||
The run length is encoded using ULEB128 (http://en.wikipedia.org/wiki/LEB128)
|
||||
|
||||
There can be more than one valid encoding, the sender may send a longer encoding
|
||||
for the benefit of reducing computation cost.
|
||||
|
||||
page = zrun nzrun
|
||||
| zrun nzrun page
|
||||
|
||||
zrun = length
|
||||
|
||||
nzrun = length byte...
|
||||
|
||||
length = uleb128 encoded integer
|
||||
|
||||
On the sender side XBZRLE is used as a compact delta encoding of page updates,
|
||||
retrieving the old page content from the cache (default size of 512 MB). The
|
||||
receiving side uses the existing page's content and XBZRLE to decode the new
|
||||
page's content.
|
||||
|
||||
This work was originally based on research results published
|
||||
VEE 2011: Evaluation of Delta Compression Techniques for Efficient Live
|
||||
Migration of Large Virtual Machines by Benoit, Svard, Tordsson and Elmroth.
|
||||
Additionally the delta encoder XBRLE was improved further using the XBZRLE
|
||||
instead.
|
||||
|
||||
XBZRLE has a sustained bandwidth of 2-2.5 GB/s for typical workloads making it
|
||||
ideal for in-line, real-time encoding such as is needed for live-migration.
|
||||
|
||||
Example
|
||||
old buffer:
|
||||
1001 zeros
|
||||
05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 68 00 00 6b 00 6d
|
||||
3074 zeros
|
||||
|
||||
new buffer:
|
||||
1001 zeros
|
||||
01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 68 00 00 67 00 69
|
||||
3074 zeros
|
||||
|
||||
encoded buffer:
|
||||
|
||||
encoded length 24
|
||||
e9 07 0f 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 03 01 67 01 01 69
|
||||
|
||||
Usage
|
||||
======================
|
||||
1. Verify the destination QEMU version is able to decode the new format.
|
||||
{qemu} info migrate_capabilities
|
||||
{qemu} xbzrle: off , ...
|
||||
|
||||
2. Activate xbzrle on both source and destination:
|
||||
{qemu} migrate_set_capability xbzrle on
|
||||
|
||||
3. Set the XBZRLE cache size - the cache size is in MBytes and should be a
|
||||
power of 2. The cache default value is 64MBytes. (on source only)
|
||||
{qemu} migrate_set_cache_size 256m
|
||||
|
||||
4. Start outgoing migration
|
||||
{qemu} migrate -d tcp:destination.host:4444
|
||||
{qemu} info migrate
|
||||
capabilities: xbzrle: on
|
||||
Migration status: active
|
||||
transferred ram: A kbytes
|
||||
remaining ram: B kbytes
|
||||
total ram: C kbytes
|
||||
total time: D milliseconds
|
||||
duplicate: E pages
|
||||
normal: F pages
|
||||
normal bytes: G kbytes
|
||||
cache size: H bytes
|
||||
xbzrle transferred: I kbytes
|
||||
xbzrle pages: J pages
|
||||
xbzrle cache miss: K
|
||||
xbzrle overflow : L
|
||||
|
||||
xbzrle cache-miss: the number of cache misses to date - high cache-miss rate
|
||||
indicates that the cache size is set too low.
|
||||
xbzrle overflow: the number of overflows in the decoding which where the delta
|
||||
could not be compressed. This can happen if the changes in the pages are too
|
||||
large or there are many short changes; for example, changing every second byte
|
||||
(half a page).
|
||||
|
||||
Testing: Testing indicated that live migration with XBZRLE was completed in 110
|
||||
seconds, whereas without it would not be able to complete.
|
||||
|
||||
A simple synthetic memory r/w load generator:
|
||||
.. include <stdlib.h>
|
||||
.. include <stdio.h>
|
||||
.. int main()
|
||||
.. {
|
||||
.. char *buf = (char *) calloc(4096, 4096);
|
||||
.. while (1) {
|
||||
.. int i;
|
||||
.. for (i = 0; i < 4096 * 4; i++) {
|
||||
.. buf[i * 4096 / 4]++;
|
||||
.. }
|
||||
.. printf(".");
|
||||
.. }
|
||||
.. }
|
@ -829,6 +829,26 @@ STEXI
|
||||
@item migrate_cancel
|
||||
@findex migrate_cancel
|
||||
Cancel the current VM migration.
|
||||
|
||||
ETEXI
|
||||
|
||||
{
|
||||
.name = "migrate_set_cache_size",
|
||||
.args_type = "value:o",
|
||||
.params = "value",
|
||||
.help = "set cache size (in bytes) for XBZRLE migrations,"
|
||||
"the cache size will be rounded down to the nearest "
|
||||
"power of 2.\n"
|
||||
"The cache size affects the number of cache misses."
|
||||
"In case of a high cache miss ratio you need to increase"
|
||||
" the cache size",
|
||||
.mhandler.cmd = hmp_migrate_set_cache_size,
|
||||
},
|
||||
|
||||
STEXI
|
||||
@item migrate_set_cache_size @var{value}
|
||||
@findex migrate_set_cache_size
|
||||
Set cache size to @var{value} (in bytes) for xbzrle migrations.
|
||||
ETEXI
|
||||
|
||||
{
|
||||
@ -858,6 +878,20 @@ STEXI
|
||||
@item migrate_set_downtime @var{second}
|
||||
@findex migrate_set_downtime
|
||||
Set maximum tolerated downtime (in seconds) for migration.
|
||||
ETEXI
|
||||
|
||||
{
|
||||
.name = "migrate_set_capability",
|
||||
.args_type = "capability:s,state:b",
|
||||
.params = "capability state",
|
||||
.help = "Enable/Disable the usage of a capability for migration",
|
||||
.mhandler.cmd = hmp_migrate_set_capability,
|
||||
},
|
||||
|
||||
STEXI
|
||||
@item migrate_set_capability @var{capability} @var{state}
|
||||
@findex migrate_set_capability
|
||||
Enable/Disable the usage of a capability @var{capability} for migration.
|
||||
ETEXI
|
||||
|
||||
{
|
||||
@ -1417,6 +1451,10 @@ show CPU statistics
|
||||
show user network stack connection states
|
||||
@item info migrate
|
||||
show migration status
|
||||
@item info migrate_capabilities
|
||||
show current migration capabilities
|
||||
@item info migrate_cache_size
|
||||
show current migration XBZRLE cache size
|
||||
@item info balloon
|
||||
show balloon information
|
||||
@item info qtree
|
||||
|
103
hmp.c
103
hmp.c
@ -131,8 +131,21 @@ void hmp_info_mice(Monitor *mon)
|
||||
void hmp_info_migrate(Monitor *mon)
|
||||
{
|
||||
MigrationInfo *info;
|
||||
MigrationCapabilityStatusList *caps, *cap;
|
||||
|
||||
info = qmp_query_migrate(NULL);
|
||||
caps = qmp_query_migrate_capabilities(NULL);
|
||||
|
||||
/* do not display parameters during setup */
|
||||
if (info->has_status && caps) {
|
||||
monitor_printf(mon, "capabilities: ");
|
||||
for (cap = caps; cap; cap = cap->next) {
|
||||
monitor_printf(mon, "%s: %s ",
|
||||
MigrationCapability_lookup[cap->value->capability],
|
||||
cap->value->state ? "on" : "off");
|
||||
}
|
||||
monitor_printf(mon, "\n");
|
||||
}
|
||||
|
||||
if (info->has_status) {
|
||||
monitor_printf(mon, "Migration status: %s\n", info->status);
|
||||
@ -147,6 +160,12 @@ void hmp_info_migrate(Monitor *mon)
|
||||
info->ram->total >> 10);
|
||||
monitor_printf(mon, "total time: %" PRIu64 " milliseconds\n",
|
||||
info->ram->total_time);
|
||||
monitor_printf(mon, "duplicate: %" PRIu64 " pages\n",
|
||||
info->ram->duplicate);
|
||||
monitor_printf(mon, "normal: %" PRIu64 " pages\n",
|
||||
info->ram->normal);
|
||||
monitor_printf(mon, "normal bytes: %" PRIu64 " kbytes\n",
|
||||
info->ram->normal_bytes >> 10);
|
||||
}
|
||||
|
||||
if (info->has_disk) {
|
||||
@ -158,7 +177,46 @@ void hmp_info_migrate(Monitor *mon)
|
||||
info->disk->total >> 10);
|
||||
}
|
||||
|
||||
if (info->has_xbzrle_cache) {
|
||||
monitor_printf(mon, "cache size: %" PRIu64 " bytes\n",
|
||||
info->xbzrle_cache->cache_size);
|
||||
monitor_printf(mon, "xbzrle transferred: %" PRIu64 " kbytes\n",
|
||||
info->xbzrle_cache->bytes >> 10);
|
||||
monitor_printf(mon, "xbzrle pages: %" PRIu64 " pages\n",
|
||||
info->xbzrle_cache->pages);
|
||||
monitor_printf(mon, "xbzrle cache miss: %" PRIu64 "\n",
|
||||
info->xbzrle_cache->cache_miss);
|
||||
monitor_printf(mon, "xbzrle overflow : %" PRIu64 "\n",
|
||||
info->xbzrle_cache->overflow);
|
||||
}
|
||||
|
||||
qapi_free_MigrationInfo(info);
|
||||
qapi_free_MigrationCapabilityStatusList(caps);
|
||||
}
|
||||
|
||||
void hmp_info_migrate_capabilities(Monitor *mon)
|
||||
{
|
||||
MigrationCapabilityStatusList *caps, *cap;
|
||||
|
||||
caps = qmp_query_migrate_capabilities(NULL);
|
||||
|
||||
if (caps) {
|
||||
monitor_printf(mon, "capabilities: ");
|
||||
for (cap = caps; cap; cap = cap->next) {
|
||||
monitor_printf(mon, "%s: %s ",
|
||||
MigrationCapability_lookup[cap->value->capability],
|
||||
cap->value->state ? "on" : "off");
|
||||
}
|
||||
monitor_printf(mon, "\n");
|
||||
}
|
||||
|
||||
qapi_free_MigrationCapabilityStatusList(caps);
|
||||
}
|
||||
|
||||
void hmp_info_migrate_cache_size(Monitor *mon)
|
||||
{
|
||||
monitor_printf(mon, "xbzrel cache size: %" PRId64 " kbytes\n",
|
||||
qmp_query_migrate_cache_size(NULL) >> 10);
|
||||
}
|
||||
|
||||
void hmp_info_cpus(Monitor *mon)
|
||||
@ -731,12 +789,57 @@ void hmp_migrate_set_downtime(Monitor *mon, const QDict *qdict)
|
||||
qmp_migrate_set_downtime(value, NULL);
|
||||
}
|
||||
|
||||
void hmp_migrate_set_cache_size(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
int64_t value = qdict_get_int(qdict, "value");
|
||||
Error *err = NULL;
|
||||
|
||||
qmp_migrate_set_cache_size(value, &err);
|
||||
if (err) {
|
||||
monitor_printf(mon, "%s\n", error_get_pretty(err));
|
||||
error_free(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void hmp_migrate_set_speed(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
int64_t value = qdict_get_int(qdict, "value");
|
||||
qmp_migrate_set_speed(value, NULL);
|
||||
}
|
||||
|
||||
void hmp_migrate_set_capability(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
const char *cap = qdict_get_str(qdict, "capability");
|
||||
bool state = qdict_get_bool(qdict, "state");
|
||||
Error *err = NULL;
|
||||
MigrationCapabilityStatusList *caps = g_malloc0(sizeof(*caps));
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MIGRATION_CAPABILITY_MAX; i++) {
|
||||
if (strcmp(cap, MigrationCapability_lookup[i]) == 0) {
|
||||
caps->value = g_malloc0(sizeof(*caps->value));
|
||||
caps->value->capability = i;
|
||||
caps->value->state = state;
|
||||
caps->next = NULL;
|
||||
qmp_migrate_set_capabilities(caps, &err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == MIGRATION_CAPABILITY_MAX) {
|
||||
error_set(&err, QERR_INVALID_PARAMETER, cap);
|
||||
}
|
||||
|
||||
qapi_free_MigrationCapabilityStatusList(caps);
|
||||
|
||||
if (err) {
|
||||
monitor_printf(mon, "migrate_set_parameter: %s\n",
|
||||
error_get_pretty(err));
|
||||
error_free(err);
|
||||
}
|
||||
}
|
||||
|
||||
void hmp_set_password(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
const char *protocol = qdict_get_str(qdict, "protocol");
|
||||
|
4
hmp.h
4
hmp.h
@ -25,6 +25,8 @@ void hmp_info_uuid(Monitor *mon);
|
||||
void hmp_info_chardev(Monitor *mon);
|
||||
void hmp_info_mice(Monitor *mon);
|
||||
void hmp_info_migrate(Monitor *mon);
|
||||
void hmp_info_migrate_capabilities(Monitor *mon);
|
||||
void hmp_info_migrate_cache_size(Monitor *mon);
|
||||
void hmp_info_cpus(Monitor *mon);
|
||||
void hmp_info_block(Monitor *mon);
|
||||
void hmp_info_blockstats(Monitor *mon);
|
||||
@ -51,6 +53,8 @@ void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict);
|
||||
void hmp_migrate_cancel(Monitor *mon, const QDict *qdict);
|
||||
void hmp_migrate_set_downtime(Monitor *mon, const QDict *qdict);
|
||||
void hmp_migrate_set_speed(Monitor *mon, const QDict *qdict);
|
||||
void hmp_migrate_set_capability(Monitor *mon, const QDict *qdict);
|
||||
void hmp_migrate_set_cache_size(Monitor *mon, const QDict *qdict);
|
||||
void hmp_set_password(Monitor *mon, const QDict *qdict);
|
||||
void hmp_expire_password(Monitor *mon, const QDict *qdict);
|
||||
void hmp_eject(Monitor *mon, const QDict *qdict);
|
||||
|
79
include/qemu/page_cache.h
Normal file
79
include/qemu/page_cache.h
Normal file
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Page cache for QEMU
|
||||
* The cache is base on a hash of the page address
|
||||
*
|
||||
* Copyright 2012 Red Hat, Inc. and/or its affiliates
|
||||
*
|
||||
* Authors:
|
||||
* Orit Wasserman <owasserm@redhat.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PAGE_CACHE_H
|
||||
#define PAGE_CACHE_H
|
||||
|
||||
/* Page cache for storing guest pages */
|
||||
typedef struct PageCache PageCache;
|
||||
|
||||
/**
|
||||
* cache_init: Initialize the page cache
|
||||
*
|
||||
*
|
||||
* Returns new allocated cache or NULL on error
|
||||
*
|
||||
* @cache pointer to the PageCache struct
|
||||
* @num_pages: cache maximal number of cached pages
|
||||
* @page_size: cache page size
|
||||
*/
|
||||
PageCache *cache_init(int64_t num_pages, unsigned int page_size);
|
||||
|
||||
/**
|
||||
* cache_fini: free all cache resources
|
||||
* @cache pointer to the PageCache struct
|
||||
*/
|
||||
void cache_fini(PageCache *cache);
|
||||
|
||||
/**
|
||||
* cache_is_cached: Checks to see if the page is cached
|
||||
*
|
||||
* Returns %true if page is cached
|
||||
*
|
||||
* @cache pointer to the PageCache struct
|
||||
* @addr: page addr
|
||||
*/
|
||||
bool cache_is_cached(const PageCache *cache, uint64_t addr);
|
||||
|
||||
/**
|
||||
* get_cached_data: Get the data cached for an addr
|
||||
*
|
||||
* Returns pointer to the data cached or NULL if not cached
|
||||
*
|
||||
* @cache pointer to the PageCache struct
|
||||
* @addr: page addr
|
||||
*/
|
||||
uint8_t *get_cached_data(const PageCache *cache, uint64_t addr);
|
||||
|
||||
/**
|
||||
* cache_insert: insert the page into the cache. the previous value will be overwritten
|
||||
*
|
||||
* @cache pointer to the PageCache struct
|
||||
* @addr: page address
|
||||
* @pdata: pointer to the page
|
||||
*/
|
||||
void cache_insert(PageCache *cache, uint64_t addr, uint8_t *pdata);
|
||||
|
||||
/**
|
||||
* cache_resize: resize the page cache. In case of size reduction the extra
|
||||
* pages will be freed
|
||||
*
|
||||
* Returns -1 on error new cache size on success
|
||||
*
|
||||
* @cache pointer to the PageCache struct
|
||||
* @num_pages: new page cache size (in pages)
|
||||
*/
|
||||
int64_t cache_resize(PageCache *cache, int64_t num_pages);
|
||||
|
||||
#endif
|
112
migration.c
112
migration.c
@ -43,6 +43,9 @@ enum {
|
||||
|
||||
#define MAX_THROTTLE (32 << 20) /* Migration speed throttling */
|
||||
|
||||
/* Migration XBZRLE default cache size */
|
||||
#define DEFAULT_MIGRATE_CACHE_SIZE (64 * 1024 * 1024)
|
||||
|
||||
static NotifierList migration_state_notifiers =
|
||||
NOTIFIER_LIST_INITIALIZER(migration_state_notifiers);
|
||||
|
||||
@ -55,6 +58,7 @@ static MigrationState *migrate_get_current(void)
|
||||
static MigrationState current_migration = {
|
||||
.state = MIG_STATE_SETUP,
|
||||
.bandwidth_limit = MAX_THROTTLE,
|
||||
.xbzrle_cache_size = DEFAULT_MIGRATE_CACHE_SIZE,
|
||||
};
|
||||
|
||||
return ¤t_migration;
|
||||
@ -113,6 +117,43 @@ uint64_t migrate_max_downtime(void)
|
||||
return max_downtime;
|
||||
}
|
||||
|
||||
MigrationCapabilityStatusList *qmp_query_migrate_capabilities(Error **errp)
|
||||
{
|
||||
MigrationCapabilityStatusList *head = NULL;
|
||||
MigrationCapabilityStatusList *caps;
|
||||
MigrationState *s = migrate_get_current();
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MIGRATION_CAPABILITY_MAX; i++) {
|
||||
if (head == NULL) {
|
||||
head = g_malloc0(sizeof(*caps));
|
||||
caps = head;
|
||||
} else {
|
||||
caps->next = g_malloc0(sizeof(*caps));
|
||||
caps = caps->next;
|
||||
}
|
||||
caps->value =
|
||||
g_malloc(sizeof(*caps->value));
|
||||
caps->value->capability = i;
|
||||
caps->value->state = s->enabled_capabilities[i];
|
||||
}
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
static void get_xbzrle_cache_stats(MigrationInfo *info)
|
||||
{
|
||||
if (migrate_use_xbzrle()) {
|
||||
info->has_xbzrle_cache = true;
|
||||
info->xbzrle_cache = g_malloc0(sizeof(*info->xbzrle_cache));
|
||||
info->xbzrle_cache->cache_size = migrate_xbzrle_cache_size();
|
||||
info->xbzrle_cache->bytes = xbzrle_mig_bytes_transferred();
|
||||
info->xbzrle_cache->pages = xbzrle_mig_pages_transferred();
|
||||
info->xbzrle_cache->cache_miss = xbzrle_mig_pages_cache_miss();
|
||||
info->xbzrle_cache->overflow = xbzrle_mig_pages_overflow();
|
||||
}
|
||||
}
|
||||
|
||||
MigrationInfo *qmp_query_migrate(Error **errp)
|
||||
{
|
||||
MigrationInfo *info = g_malloc0(sizeof(*info));
|
||||
@ -133,6 +174,9 @@ MigrationInfo *qmp_query_migrate(Error **errp)
|
||||
info->ram->total = ram_bytes_total();
|
||||
info->ram->total_time = qemu_get_clock_ms(rt_clock)
|
||||
- s->total_time;
|
||||
info->ram->duplicate = dup_mig_pages_transferred();
|
||||
info->ram->normal = norm_mig_pages_transferred();
|
||||
info->ram->normal_bytes = norm_mig_bytes_transferred();
|
||||
|
||||
if (blk_mig_active()) {
|
||||
info->has_disk = true;
|
||||
@ -141,8 +185,12 @@ MigrationInfo *qmp_query_migrate(Error **errp)
|
||||
info->disk->remaining = blk_mig_bytes_remaining();
|
||||
info->disk->total = blk_mig_bytes_total();
|
||||
}
|
||||
|
||||
get_xbzrle_cache_stats(info);
|
||||
break;
|
||||
case MIG_STATE_COMPLETED:
|
||||
get_xbzrle_cache_stats(info);
|
||||
|
||||
info->has_status = true;
|
||||
info->status = g_strdup("completed");
|
||||
|
||||
@ -152,6 +200,9 @@ MigrationInfo *qmp_query_migrate(Error **errp)
|
||||
info->ram->remaining = 0;
|
||||
info->ram->total = ram_bytes_total();
|
||||
info->ram->total_time = s->total_time;
|
||||
info->ram->duplicate = dup_mig_pages_transferred();
|
||||
info->ram->normal = norm_mig_pages_transferred();
|
||||
info->ram->normal_bytes = norm_mig_bytes_transferred();
|
||||
break;
|
||||
case MIG_STATE_ERROR:
|
||||
info->has_status = true;
|
||||
@ -166,6 +217,22 @@ MigrationInfo *qmp_query_migrate(Error **errp)
|
||||
return info;
|
||||
}
|
||||
|
||||
void qmp_migrate_set_capabilities(MigrationCapabilityStatusList *params,
|
||||
Error **errp)
|
||||
{
|
||||
MigrationState *s = migrate_get_current();
|
||||
MigrationCapabilityStatusList *cap;
|
||||
|
||||
if (s->state == MIG_STATE_ACTIVE) {
|
||||
error_set(errp, QERR_MIGRATION_ACTIVE);
|
||||
return;
|
||||
}
|
||||
|
||||
for (cap = params; cap; cap = cap->next) {
|
||||
s->enabled_capabilities[cap->value->capability] = cap->value->state;
|
||||
}
|
||||
}
|
||||
|
||||
/* shared migration helpers */
|
||||
|
||||
static int migrate_fd_cleanup(MigrationState *s)
|
||||
@ -375,10 +442,18 @@ static MigrationState *migrate_init(const MigrationParams *params)
|
||||
{
|
||||
MigrationState *s = migrate_get_current();
|
||||
int64_t bandwidth_limit = s->bandwidth_limit;
|
||||
bool enabled_capabilities[MIGRATION_CAPABILITY_MAX];
|
||||
int64_t xbzrle_cache_size = s->xbzrle_cache_size;
|
||||
|
||||
memcpy(enabled_capabilities, s->enabled_capabilities,
|
||||
sizeof(enabled_capabilities));
|
||||
|
||||
memset(s, 0, sizeof(*s));
|
||||
s->bandwidth_limit = bandwidth_limit;
|
||||
s->params = *params;
|
||||
memcpy(s->enabled_capabilities, enabled_capabilities,
|
||||
sizeof(enabled_capabilities));
|
||||
s->xbzrle_cache_size = xbzrle_cache_size;
|
||||
|
||||
s->bandwidth_limit = bandwidth_limit;
|
||||
s->state = MIG_STATE_SETUP;
|
||||
@ -459,6 +534,25 @@ void qmp_migrate_cancel(Error **errp)
|
||||
migrate_fd_cancel(migrate_get_current());
|
||||
}
|
||||
|
||||
void qmp_migrate_set_cache_size(int64_t value, Error **errp)
|
||||
{
|
||||
MigrationState *s = migrate_get_current();
|
||||
|
||||
/* Check for truncation */
|
||||
if (value != (size_t)value) {
|
||||
error_set(errp, QERR_INVALID_PARAMETER_VALUE, "cache size",
|
||||
"exceeding address space");
|
||||
return;
|
||||
}
|
||||
|
||||
s->xbzrle_cache_size = xbzrle_cache_resize(value);
|
||||
}
|
||||
|
||||
int64_t qmp_query_migrate_cache_size(Error **errp)
|
||||
{
|
||||
return migrate_xbzrle_cache_size();
|
||||
}
|
||||
|
||||
void qmp_migrate_set_speed(int64_t value, Error **errp)
|
||||
{
|
||||
MigrationState *s;
|
||||
@ -478,3 +572,21 @@ void qmp_migrate_set_downtime(double value, Error **errp)
|
||||
value = MAX(0, MIN(UINT64_MAX, value));
|
||||
max_downtime = (uint64_t)value;
|
||||
}
|
||||
|
||||
int migrate_use_xbzrle(void)
|
||||
{
|
||||
MigrationState *s;
|
||||
|
||||
s = migrate_get_current();
|
||||
|
||||
return s->enabled_capabilities[MIGRATION_CAPABILITY_XBZRLE];
|
||||
}
|
||||
|
||||
int64_t migrate_xbzrle_cache_size(void)
|
||||
{
|
||||
MigrationState *s;
|
||||
|
||||
s = migrate_get_current();
|
||||
|
||||
return s->xbzrle_cache_size;
|
||||
}
|
||||
|
21
migration.h
21
migration.h
@ -19,6 +19,7 @@
|
||||
#include "notify.h"
|
||||
#include "error.h"
|
||||
#include "vmstate.h"
|
||||
#include "qapi-types.h"
|
||||
|
||||
struct MigrationParams {
|
||||
bool blk;
|
||||
@ -39,6 +40,8 @@ struct MigrationState
|
||||
void *opaque;
|
||||
MigrationParams params;
|
||||
int64_t total_time;
|
||||
bool enabled_capabilities[MIGRATION_CAPABILITY_MAX];
|
||||
int64_t xbzrle_cache_size;
|
||||
};
|
||||
|
||||
void process_incoming_migration(QEMUFile *f);
|
||||
@ -84,6 +87,15 @@ uint64_t ram_bytes_total(void);
|
||||
|
||||
extern SaveVMHandlers savevm_ram_handlers;
|
||||
|
||||
uint64_t dup_mig_bytes_transferred(void);
|
||||
uint64_t dup_mig_pages_transferred(void);
|
||||
uint64_t norm_mig_bytes_transferred(void);
|
||||
uint64_t norm_mig_pages_transferred(void);
|
||||
uint64_t xbzrle_mig_bytes_transferred(void);
|
||||
uint64_t xbzrle_mig_pages_transferred(void);
|
||||
uint64_t xbzrle_mig_pages_overflow(void);
|
||||
uint64_t xbzrle_mig_pages_cache_miss(void);
|
||||
|
||||
/**
|
||||
* @migrate_add_blocker - prevent migration from proceeding
|
||||
*
|
||||
@ -98,4 +110,13 @@ void migrate_add_blocker(Error *reason);
|
||||
*/
|
||||
void migrate_del_blocker(Error *reason);
|
||||
|
||||
int xbzrle_encode_buffer(uint8_t *old_buf, uint8_t *new_buf, int slen,
|
||||
uint8_t *dst, int dlen);
|
||||
int xbzrle_decode_buffer(uint8_t *src, int slen, uint8_t *dst, int dlen);
|
||||
|
||||
int migrate_use_xbzrle(void);
|
||||
int64_t migrate_xbzrle_cache_size(void);
|
||||
|
||||
int64_t xbzrle_cache_resize(int64_t new_size);
|
||||
|
||||
#endif
|
||||
|
14
monitor.c
14
monitor.c
@ -2654,6 +2654,20 @@ static mon_cmd_t info_cmds[] = {
|
||||
.help = "show migration status",
|
||||
.mhandler.info = hmp_info_migrate,
|
||||
},
|
||||
{
|
||||
.name = "migrate_capabilities",
|
||||
.args_type = "",
|
||||
.params = "",
|
||||
.help = "show current migration capabilities",
|
||||
.mhandler.info = hmp_info_migrate_capabilities,
|
||||
},
|
||||
{
|
||||
.name = "migrate_cache_size",
|
||||
.args_type = "",
|
||||
.params = "",
|
||||
.help = "show current migration xbzrle cache size",
|
||||
.mhandler.info = hmp_info_migrate_cache_size,
|
||||
},
|
||||
{
|
||||
.name = "balloon",
|
||||
.args_type = "",
|
||||
|
218
page_cache.c
Normal file
218
page_cache.c
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Page cache for QEMU
|
||||
* The cache is base on a hash of the page address
|
||||
*
|
||||
* Copyright 2012 Red Hat, Inc. and/or its affiliates
|
||||
*
|
||||
* Authors:
|
||||
* Orit Wasserman <owasserm@redhat.com>
|
||||
*
|
||||
* This work is licensed under the terms of the GNU GPL, version 2 or later.
|
||||
* See the COPYING file in the top-level directory.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <strings.h>
|
||||
#include <string.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdbool.h>
|
||||
#include <glib.h>
|
||||
#include <strings.h>
|
||||
|
||||
#include "qemu-common.h"
|
||||
#include "qemu/page_cache.h"
|
||||
|
||||
#ifdef DEBUG_CACHE
|
||||
#define DPRINTF(fmt, ...) \
|
||||
do { fprintf(stdout, "cache: " fmt, ## __VA_ARGS__); } while (0)
|
||||
#else
|
||||
#define DPRINTF(fmt, ...) \
|
||||
do { } while (0)
|
||||
#endif
|
||||
|
||||
typedef struct CacheItem CacheItem;
|
||||
|
||||
struct CacheItem {
|
||||
uint64_t it_addr;
|
||||
uint64_t it_age;
|
||||
uint8_t *it_data;
|
||||
};
|
||||
|
||||
struct PageCache {
|
||||
CacheItem *page_cache;
|
||||
unsigned int page_size;
|
||||
int64_t max_num_items;
|
||||
uint64_t max_item_age;
|
||||
int64_t num_items;
|
||||
};
|
||||
|
||||
PageCache *cache_init(int64_t num_pages, unsigned int page_size)
|
||||
{
|
||||
int64_t i;
|
||||
|
||||
PageCache *cache;
|
||||
|
||||
if (num_pages <= 0) {
|
||||
DPRINTF("invalid number of pages\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cache = g_malloc(sizeof(*cache));
|
||||
|
||||
/* round down to the nearest power of 2 */
|
||||
if (!is_power_of_2(num_pages)) {
|
||||
num_pages = pow2floor(num_pages);
|
||||
DPRINTF("rounding down to %" PRId64 "\n", num_pages);
|
||||
}
|
||||
cache->page_size = page_size;
|
||||
cache->num_items = 0;
|
||||
cache->max_item_age = 0;
|
||||
cache->max_num_items = num_pages;
|
||||
|
||||
DPRINTF("Setting cache buckets to %" PRId64 "\n", cache->max_num_items);
|
||||
|
||||
cache->page_cache = g_malloc((cache->max_num_items) *
|
||||
sizeof(*cache->page_cache));
|
||||
|
||||
for (i = 0; i < cache->max_num_items; i++) {
|
||||
cache->page_cache[i].it_data = NULL;
|
||||
cache->page_cache[i].it_age = 0;
|
||||
cache->page_cache[i].it_addr = -1;
|
||||
}
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
void cache_fini(PageCache *cache)
|
||||
{
|
||||
int64_t i;
|
||||
|
||||
g_assert(cache);
|
||||
g_assert(cache->page_cache);
|
||||
|
||||
for (i = 0; i < cache->max_num_items; i++) {
|
||||
g_free(cache->page_cache[i].it_data);
|
||||
}
|
||||
|
||||
g_free(cache->page_cache);
|
||||
cache->page_cache = NULL;
|
||||
}
|
||||
|
||||
static size_t cache_get_cache_pos(const PageCache *cache,
|
||||
uint64_t address)
|
||||
{
|
||||
size_t pos;
|
||||
|
||||
g_assert(cache->max_num_items);
|
||||
pos = (address / cache->page_size) & (cache->max_num_items - 1);
|
||||
return pos;
|
||||
}
|
||||
|
||||
bool cache_is_cached(const PageCache *cache, uint64_t addr)
|
||||
{
|
||||
size_t pos;
|
||||
|
||||
g_assert(cache);
|
||||
g_assert(cache->page_cache);
|
||||
|
||||
pos = cache_get_cache_pos(cache, addr);
|
||||
|
||||
return (cache->page_cache[pos].it_addr == addr);
|
||||
}
|
||||
|
||||
static CacheItem *cache_get_by_addr(const PageCache *cache, uint64_t addr)
|
||||
{
|
||||
size_t pos;
|
||||
|
||||
g_assert(cache);
|
||||
g_assert(cache->page_cache);
|
||||
|
||||
pos = cache_get_cache_pos(cache, addr);
|
||||
|
||||
return &cache->page_cache[pos];
|
||||
}
|
||||
|
||||
uint8_t *get_cached_data(const PageCache *cache, uint64_t addr)
|
||||
{
|
||||
return cache_get_by_addr(cache, addr)->it_data;
|
||||
}
|
||||
|
||||
void cache_insert(PageCache *cache, uint64_t addr, uint8_t *pdata)
|
||||
{
|
||||
|
||||
CacheItem *it = NULL;
|
||||
|
||||
g_assert(cache);
|
||||
g_assert(cache->page_cache);
|
||||
|
||||
/* actual update of entry */
|
||||
it = cache_get_by_addr(cache, addr);
|
||||
|
||||
if (!it->it_data) {
|
||||
cache->num_items++;
|
||||
}
|
||||
|
||||
it->it_data = pdata;
|
||||
it->it_age = ++cache->max_item_age;
|
||||
it->it_addr = addr;
|
||||
}
|
||||
|
||||
int64_t cache_resize(PageCache *cache, int64_t new_num_pages)
|
||||
{
|
||||
PageCache *new_cache;
|
||||
int64_t i;
|
||||
|
||||
CacheItem *old_it, *new_it;
|
||||
|
||||
g_assert(cache);
|
||||
|
||||
/* cache was not inited */
|
||||
if (cache->page_cache == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* same size */
|
||||
if (pow2floor(new_num_pages) == cache->max_num_items) {
|
||||
return cache->max_num_items;
|
||||
}
|
||||
|
||||
new_cache = cache_init(new_num_pages, cache->page_size);
|
||||
if (!(new_cache)) {
|
||||
DPRINTF("Error creating new cache\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* move all data from old cache */
|
||||
for (i = 0; i < cache->max_num_items; i++) {
|
||||
old_it = &cache->page_cache[i];
|
||||
if (old_it->it_addr != -1) {
|
||||
/* check for collision, if there is, keep MRU page */
|
||||
new_it = cache_get_by_addr(new_cache, old_it->it_addr);
|
||||
if (new_it->it_data) {
|
||||
/* keep the MRU page */
|
||||
if (new_it->it_age >= old_it->it_age) {
|
||||
g_free(old_it->it_data);
|
||||
} else {
|
||||
g_free(new_it->it_data);
|
||||
new_it->it_data = old_it->it_data;
|
||||
new_it->it_age = old_it->it_age;
|
||||
new_it->it_addr = old_it->it_addr;
|
||||
}
|
||||
} else {
|
||||
cache_insert(new_cache, old_it->it_addr, old_it->it_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache->page_cache = new_cache->page_cache;
|
||||
cache->max_num_items = new_cache->max_num_items;
|
||||
cache->num_items = new_cache->num_items;
|
||||
|
||||
g_free(new_cache);
|
||||
|
||||
return cache->max_num_items;
|
||||
}
|
119
qapi-schema.json
119
qapi-schema.json
@ -260,15 +260,43 @@
|
||||
#
|
||||
# @total: total amount of bytes involved in the migration process
|
||||
#
|
||||
# @total_time: tota0l amount of ms since migration started. If
|
||||
# @total-time: total amount of ms since migration started. If
|
||||
# migration has ended, it returns the total migration
|
||||
# time. (since 1.2)
|
||||
#
|
||||
# Since: 0.14.0.
|
||||
# @duplicate: number of duplicate pages (since 1.2)
|
||||
#
|
||||
# @normal : number of normal pages (since 1.2)
|
||||
#
|
||||
# @normal-bytes : number of normal bytes sent (since 1.2)
|
||||
#
|
||||
# Since: 0.14.0
|
||||
##
|
||||
{ 'type': 'MigrationStats',
|
||||
'data': {'transferred': 'int', 'remaining': 'int', 'total': 'int' ,
|
||||
'total_time': 'int' } }
|
||||
'total-time': 'int', 'duplicate': 'int', 'normal': 'int',
|
||||
'normal-bytes': 'int' } }
|
||||
|
||||
##
|
||||
# @XBZRLECacheStats
|
||||
#
|
||||
# Detailed XBZRLE migration cache statistics
|
||||
#
|
||||
# @cache-size: XBZRLE cache size
|
||||
#
|
||||
# @bytes: amount of bytes already transferred to the target VM
|
||||
#
|
||||
# @pages: amount of pages transferred to the target VM
|
||||
#
|
||||
# @cache-miss: number of cache miss
|
||||
#
|
||||
# @overflow: number of overflows
|
||||
#
|
||||
# Since: 1.2
|
||||
##
|
||||
{ 'type': 'XBZRLECacheStats',
|
||||
'data': {'cache-size': 'int', 'bytes': 'int', 'pages': 'int',
|
||||
'cache-miss': 'int', 'overflow': 'int' } }
|
||||
|
||||
##
|
||||
# @MigrationInfo
|
||||
@ -288,11 +316,16 @@
|
||||
# status, only returned if status is 'active' and it is a block
|
||||
# migration
|
||||
#
|
||||
# @xbzrle-cache: #optional @XBZRLECacheStats containing detailed XBZRLE
|
||||
# migration statistics, only returned if XBZRLE feature is on and
|
||||
# status is 'active' or 'completed' (since 1.2)
|
||||
#
|
||||
# Since: 0.14.0
|
||||
##
|
||||
{ 'type': 'MigrationInfo',
|
||||
'data': {'*status': 'str', '*ram': 'MigrationStats',
|
||||
'*disk': 'MigrationStats'} }
|
||||
'*disk': 'MigrationStats',
|
||||
'*xbzrle-cache': 'XBZRLECacheStats'} }
|
||||
|
||||
##
|
||||
# @query-migrate
|
||||
@ -305,6 +338,57 @@
|
||||
##
|
||||
{ 'command': 'query-migrate', 'returns': 'MigrationInfo' }
|
||||
|
||||
##
|
||||
# @MigrationCapability
|
||||
#
|
||||
# Migration capabilities enumeration
|
||||
#
|
||||
# @xbzrle: Migration supports xbzrle (Xor Based Zero Run Length Encoding).
|
||||
# This feature allows us to minimize migration traffic for certain work
|
||||
# loads, by sending compressed difference of the pages
|
||||
#
|
||||
# Since: 1.2
|
||||
##
|
||||
{ 'enum': 'MigrationCapability',
|
||||
'data': ['xbzrle'] }
|
||||
|
||||
##
|
||||
# @MigrationCapabilityStatus
|
||||
#
|
||||
# Migration capability information
|
||||
#
|
||||
# @capability: capability enum
|
||||
#
|
||||
# @state: capability state bool
|
||||
#
|
||||
# Since: 1.2
|
||||
##
|
||||
{ 'type': 'MigrationCapabilityStatus',
|
||||
'data': { 'capability' : 'MigrationCapability', 'state' : 'bool' } }
|
||||
|
||||
##
|
||||
# @migrate-set-capabilities
|
||||
#
|
||||
# Enable/Disable the following migration capabilities (like xbzrle)
|
||||
#
|
||||
# @capabilities: json array of capability modifications to make
|
||||
#
|
||||
# Since: 1.2
|
||||
##
|
||||
{ 'command': 'migrate-set-capabilities',
|
||||
'data': { 'capabilities': ['MigrationCapabilityStatus'] } }
|
||||
|
||||
##
|
||||
# @query-migrate-capabilities
|
||||
#
|
||||
# Returns information about the current migration capabilities status
|
||||
#
|
||||
# Returns: @MigrationCapabilitiesStatus
|
||||
#
|
||||
# Since: 1.2
|
||||
##
|
||||
{ 'command': 'query-migrate-capabilities', 'returns': ['MigrationCapabilityStatus']}
|
||||
|
||||
##
|
||||
# @MouseInfo:
|
||||
#
|
||||
@ -1343,6 +1427,33 @@
|
||||
##
|
||||
{ 'command': 'migrate_set_speed', 'data': {'value': 'int'} }
|
||||
|
||||
##
|
||||
# @migrate-set-cache-size
|
||||
#
|
||||
# Set XBZRLE cache size
|
||||
#
|
||||
# @value: cache size in bytes
|
||||
#
|
||||
# The size will be rounded down to the nearest power of 2.
|
||||
# The cache size can be modified before and during ongoing migration
|
||||
#
|
||||
# Returns: nothing on success
|
||||
#
|
||||
# Since: 1.2
|
||||
##
|
||||
{ 'command': 'migrate-set-cache-size', 'data': {'value': 'int'} }
|
||||
|
||||
##
|
||||
# @query-migrate-cache-size
|
||||
#
|
||||
# query XBZRLE cache size
|
||||
#
|
||||
# Returns: XBZRLE cache size in bytes
|
||||
#
|
||||
# Since: 1.2
|
||||
##
|
||||
{ 'command': 'query-migrate-cache-size', 'returns': 'int' }
|
||||
|
||||
##
|
||||
# @ObjectPropertyInfo:
|
||||
#
|
||||
|
@ -1,3 +1,4 @@
|
||||
|
||||
/* Common header file that is included by all of qemu. */
|
||||
#ifndef QEMU_COMMON_H
|
||||
#define QEMU_COMMON_H
|
||||
@ -429,6 +430,26 @@ static inline uint64_t muldiv64(uint64_t a, uint32_t b, uint32_t c)
|
||||
/* Round number up to multiple */
|
||||
#define QEMU_ALIGN_UP(n, m) QEMU_ALIGN_DOWN((n) + (m) - 1, (m))
|
||||
|
||||
static inline bool is_power_of_2(uint64_t value)
|
||||
{
|
||||
if (!value) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return !(value & (value - 1));
|
||||
}
|
||||
|
||||
/* round down to the nearest power of 2*/
|
||||
int64_t pow2floor(int64_t value);
|
||||
|
||||
#include "module.h"
|
||||
|
||||
/*
|
||||
* Implementation of ULEB128 (http://en.wikipedia.org/wiki/LEB128)
|
||||
* Input is limited to 14-bit numbers
|
||||
*/
|
||||
|
||||
int uleb128_encode_small(uint8_t *out, uint32_t n);
|
||||
int uleb128_decode_small(const uint8_t *in, uint32_t *n);
|
||||
|
||||
#endif
|
||||
|
159
qmp-commands.hx
159
qmp-commands.hx
@ -519,6 +519,50 @@ Example:
|
||||
-> { "execute": "migrate_cancel" }
|
||||
<- { "return": {} }
|
||||
|
||||
EQMP
|
||||
{
|
||||
.name = "migrate-set-cache-size",
|
||||
.args_type = "value:o",
|
||||
.mhandler.cmd_new = qmp_marshal_input_migrate_set_cache_size,
|
||||
},
|
||||
|
||||
SQMP
|
||||
migrate-set-cache-size
|
||||
---------------------
|
||||
|
||||
Set cache size to be used by XBZRLE migration, the cache size will be rounded
|
||||
down to the nearest power of 2
|
||||
|
||||
Arguments:
|
||||
|
||||
- "value": cache size in bytes (json-int)
|
||||
|
||||
Example:
|
||||
|
||||
-> { "execute": "migrate-set-cache-size", "arguments": { "value": 536870912 } }
|
||||
<- { "return": {} }
|
||||
|
||||
EQMP
|
||||
{
|
||||
.name = "query-migrate-cache-size",
|
||||
.args_type = "",
|
||||
.mhandler.cmd_new = qmp_marshal_input_query_migrate_cache_size,
|
||||
},
|
||||
|
||||
SQMP
|
||||
query-migrate-cache-size
|
||||
---------------------
|
||||
|
||||
Show cache size to be used by XBZRLE migration
|
||||
|
||||
returns a json-object with the following information:
|
||||
- "size" : json-int
|
||||
|
||||
Example:
|
||||
|
||||
-> { "execute": "query-migrate-cache-size" }
|
||||
<- { "return": 67108864 }
|
||||
|
||||
EQMP
|
||||
|
||||
{
|
||||
@ -2078,12 +2122,24 @@ The main json-object contains the following:
|
||||
- "transferred": amount transferred (json-int)
|
||||
- "remaining": amount remaining (json-int)
|
||||
- "total": total (json-int)
|
||||
- "total-time": total amount of ms since migration started. If
|
||||
migration has ended, it returns the total migration time
|
||||
(json-int)
|
||||
- "duplicate": number of duplicated pages (json-int)
|
||||
- "normal" : number of normal pages transferred (json-int)
|
||||
- "normal-bytes" : number of normal bytes transferred (json-int)
|
||||
- "disk": only present if "status" is "active" and it is a block migration,
|
||||
it is a json-object with the following disk information (in bytes):
|
||||
- "transferred": amount transferred (json-int)
|
||||
- "remaining": amount remaining (json-int)
|
||||
- "total": total (json-int)
|
||||
|
||||
- "xbzrle-cache": only present if XBZRLE is active.
|
||||
It is a json-object with the following XBZRLE information:
|
||||
- "cache-size": XBZRLE cache size
|
||||
- "bytes": total XBZRLE bytes transferred
|
||||
- "pages": number of XBZRLE compressed pages
|
||||
- "cache-miss": number of cache misses
|
||||
- "overflow": number of XBZRLE overflows
|
||||
Examples:
|
||||
|
||||
1. Before the first migration
|
||||
@ -2094,7 +2150,19 @@ Examples:
|
||||
2. Migration is done and has succeeded
|
||||
|
||||
-> { "execute": "query-migrate" }
|
||||
<- { "return": { "status": "completed" } }
|
||||
<- { "return": {
|
||||
"status": "completed",
|
||||
"ram":{
|
||||
"transferred":123,
|
||||
"remaining":123,
|
||||
"total":246,
|
||||
"total-time":12345,
|
||||
"duplicate":123,
|
||||
"normal":123,
|
||||
"normal-bytes":123456
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3. Migration is done and has failed
|
||||
|
||||
@ -2110,7 +2178,11 @@ Examples:
|
||||
"ram":{
|
||||
"transferred":123,
|
||||
"remaining":123,
|
||||
"total":246
|
||||
"total":246,
|
||||
"total-time":12345,
|
||||
"duplicate":123,
|
||||
"normal":123,
|
||||
"normal-bytes":123456
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2124,7 +2196,11 @@ Examples:
|
||||
"ram":{
|
||||
"total":1057024,
|
||||
"remaining":1053304,
|
||||
"transferred":3720
|
||||
"transferred":3720,
|
||||
"total-time":12345,
|
||||
"duplicate":123,
|
||||
"normal":123,
|
||||
"normal-bytes":123456
|
||||
},
|
||||
"disk":{
|
||||
"total":20971520,
|
||||
@ -2134,6 +2210,32 @@ Examples:
|
||||
}
|
||||
}
|
||||
|
||||
6. Migration is being performed and XBZRLE is active:
|
||||
|
||||
-> { "execute": "query-migrate" }
|
||||
<- {
|
||||
"return":{
|
||||
"status":"active",
|
||||
"capabilities" : [ { "capability": "xbzrle", "state" : true } ],
|
||||
"ram":{
|
||||
"total":1057024,
|
||||
"remaining":1053304,
|
||||
"transferred":3720,
|
||||
"total-time":12345,
|
||||
"duplicate":10,
|
||||
"normal":3333,
|
||||
"normal-bytes":3412992
|
||||
},
|
||||
"xbzrle-cache":{
|
||||
"cache-size":67108864,
|
||||
"bytes":20971520,
|
||||
"pages":2444343,
|
||||
"cache-miss":2244,
|
||||
"overflow":34434
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EQMP
|
||||
|
||||
{
|
||||
@ -2142,6 +2244,55 @@ EQMP
|
||||
.mhandler.cmd_new = qmp_marshal_input_query_migrate,
|
||||
},
|
||||
|
||||
SQMP
|
||||
migrate-set-capabilities
|
||||
-------
|
||||
|
||||
Enable/Disable migration capabilities
|
||||
|
||||
- "xbzrle": xbzrle support
|
||||
|
||||
Arguments:
|
||||
|
||||
Example:
|
||||
|
||||
-> { "execute": "migrate-set-capabilities" , "arguments":
|
||||
{ "capabilities": [ { "capability": "xbzrle", "state": true } ] } }
|
||||
|
||||
EQMP
|
||||
|
||||
{
|
||||
.name = "migrate-set-capabilities",
|
||||
.args_type = "capabilities:O",
|
||||
.params = "capability:s,state:b",
|
||||
.mhandler.cmd_new = qmp_marshal_input_migrate_set_capabilities,
|
||||
},
|
||||
SQMP
|
||||
query-migrate-capabilities
|
||||
-------
|
||||
|
||||
Query current migration capabilities
|
||||
|
||||
- "capabilities": migration capabilities state
|
||||
- "xbzrle" : XBZRLE state (json-bool)
|
||||
|
||||
Arguments:
|
||||
|
||||
Example:
|
||||
|
||||
-> { "execute": "query-migrate-capabilities" }
|
||||
<- { "return": {
|
||||
"capabilities" : [ { "capability" : "xbzrle", "state" : false } ]
|
||||
}
|
||||
}
|
||||
EQMP
|
||||
|
||||
{
|
||||
.name = "query-migrate-capabilities",
|
||||
.args_type = "",
|
||||
.mhandler.cmd_new = qmp_marshal_input_query_migrate_capabilities,
|
||||
},
|
||||
|
||||
SQMP
|
||||
query-balloon
|
||||
-------------
|
||||
|
159
savevm.c
159
savevm.c
@ -2392,3 +2392,162 @@ void vmstate_register_ram_global(MemoryRegion *mr)
|
||||
{
|
||||
vmstate_register_ram(mr, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
page = zrun nzrun
|
||||
| zrun nzrun page
|
||||
|
||||
zrun = length
|
||||
|
||||
nzrun = length byte...
|
||||
|
||||
length = uleb128 encoded integer
|
||||
*/
|
||||
int xbzrle_encode_buffer(uint8_t *old_buf, uint8_t *new_buf, int slen,
|
||||
uint8_t *dst, int dlen)
|
||||
{
|
||||
uint32_t zrun_len = 0, nzrun_len = 0;
|
||||
int d = 0, i = 0;
|
||||
long res, xor;
|
||||
uint8_t *nzrun_start = NULL;
|
||||
|
||||
g_assert(!(((uintptr_t)old_buf | (uintptr_t)new_buf | slen) %
|
||||
sizeof(long)));
|
||||
|
||||
while (i < slen) {
|
||||
/* overflow */
|
||||
if (d + 2 > dlen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* not aligned to sizeof(long) */
|
||||
res = (slen - i) % sizeof(long);
|
||||
while (res && old_buf[i] == new_buf[i]) {
|
||||
zrun_len++;
|
||||
i++;
|
||||
res--;
|
||||
}
|
||||
|
||||
/* word at a time for speed */
|
||||
if (!res) {
|
||||
while (i < slen &&
|
||||
(*(long *)(old_buf + i)) == (*(long *)(new_buf + i))) {
|
||||
i += sizeof(long);
|
||||
zrun_len += sizeof(long);
|
||||
}
|
||||
|
||||
/* go over the rest */
|
||||
while (i < slen && old_buf[i] == new_buf[i]) {
|
||||
zrun_len++;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
/* buffer unchanged */
|
||||
if (zrun_len == slen) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* skip last zero run */
|
||||
if (i == slen) {
|
||||
return d;
|
||||
}
|
||||
|
||||
d += uleb128_encode_small(dst + d, zrun_len);
|
||||
|
||||
zrun_len = 0;
|
||||
nzrun_start = new_buf + i;
|
||||
|
||||
/* overflow */
|
||||
if (d + 2 > dlen) {
|
||||
return -1;
|
||||
}
|
||||
/* not aligned to sizeof(long) */
|
||||
res = (slen - i) % sizeof(long);
|
||||
while (res && old_buf[i] != new_buf[i]) {
|
||||
i++;
|
||||
nzrun_len++;
|
||||
res--;
|
||||
}
|
||||
|
||||
/* word at a time for speed, use of 32-bit long okay */
|
||||
if (!res) {
|
||||
/* truncation to 32-bit long okay */
|
||||
long mask = 0x0101010101010101ULL;
|
||||
while (i < slen) {
|
||||
xor = *(long *)(old_buf + i) ^ *(long *)(new_buf + i);
|
||||
if ((xor - mask) & ~xor & (mask << 7)) {
|
||||
/* found the end of an nzrun within the current long */
|
||||
while (old_buf[i] != new_buf[i]) {
|
||||
nzrun_len++;
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
i += sizeof(long);
|
||||
nzrun_len += sizeof(long);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d += uleb128_encode_small(dst + d, nzrun_len);
|
||||
/* overflow */
|
||||
if (d + nzrun_len > dlen) {
|
||||
return -1;
|
||||
}
|
||||
memcpy(dst + d, nzrun_start, nzrun_len);
|
||||
d += nzrun_len;
|
||||
nzrun_len = 0;
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
int xbzrle_decode_buffer(uint8_t *src, int slen, uint8_t *dst, int dlen)
|
||||
{
|
||||
int i = 0, d = 0;
|
||||
int ret;
|
||||
uint32_t count = 0;
|
||||
|
||||
while (i < slen) {
|
||||
|
||||
/* zrun */
|
||||
if ((slen - i) < 2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = uleb128_decode_small(src + i, &count);
|
||||
if (ret < 0 || (i && !count)) {
|
||||
return -1;
|
||||
}
|
||||
i += ret;
|
||||
d += count;
|
||||
|
||||
/* overflow */
|
||||
if (d > dlen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* nzrun */
|
||||
if ((slen - i) < 2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = uleb128_decode_small(src + i, &count);
|
||||
if (ret < 0 || !count) {
|
||||
return -1;
|
||||
}
|
||||
i += ret;
|
||||
|
||||
/* overflow */
|
||||
if (d + count > dlen || i + count > slen) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(dst + d, src + i, count);
|
||||
d += count;
|
||||
i += count;
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user