mirror of
https://github.com/qemu/qemu.git
synced 2026-07-08 17:46:10 +00:00
Like in the normal ram_load() path, put the received pages into the colo cache and mark the pages in the bitmap so that they will be flushed to the guest later. Multifd with COLO is useful to reduce the VM pause time during checkpointing for latency sensitive workloads. In such workloads the worst-case latency is especially important. Also, this is already worth it for the precopy phase as it helps with converging. Moreover, multifd migration is the preferred way to do migration nowadays and this allows to use multifd compression with COLO. Benchmark: Cluster nodes - Intel Xenon E5-2630 v3 - 48Gb RAM - 10G Ethernet Guest - Windows Server 2016 - 6Gb RAM - 4 cores Workload - Upload a file to the guest with SMB to simulate moderate memory dirtying - Measure the memory transfer time portion of each checkpoint - 600ms COLO checkpoint interval Results Plain idle mean: 4.50ms 99per: 10.33ms load mean: 24.30ms 99per: 78.05ms Multifd-4 idle mean: 6.48ms 99per: 10.41ms load mean: 14.12ms 99per: 31.27ms Evaluation While multifd has slightly higher latency when the guest idles, it is 10ms faster under load and more importantly it's worst case latency is less than 1/2 of plain under load as can be seen in the 99. Percentile. Co-authored-by: Juan Quintela <quintela@redhat.com> [farosas: changed SoB to coauthored as Juan doesn't own that email address anymore] Reviewed-by: Fabiano Rosas <farosas@suse.de> Reviewed-by: Peter Xu <peterx@redhat.com> Signed-off-by: Lukas Straub <lukasstraub2@web.de> Link: https://lore.kernel.org/qemu-devel/20260302-colo_unit_test_multifd-v11-8-d653fb3b1d80@web.de [removed license boilerplate] Signed-off-by: Fabiano Rosas <farosas@suse.de>
422 lines
12 KiB
C
422 lines
12 KiB
C
/*
|
|
* Multifd common functions
|
|
*
|
|
* Copyright (c) 2019-2020 Red Hat Inc
|
|
*
|
|
* Authors:
|
|
* Juan Quintela <quintela@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 QEMU_MIGRATION_MULTIFD_H
|
|
#define QEMU_MIGRATION_MULTIFD_H
|
|
|
|
#include "exec/target_page.h"
|
|
#include "ram.h"
|
|
|
|
typedef struct MultiFDRecvData MultiFDRecvData;
|
|
typedef struct MultiFDSendData MultiFDSendData;
|
|
|
|
typedef enum {
|
|
/* No sync request */
|
|
MULTIFD_SYNC_NONE = 0,
|
|
/* Sync locally on the sender threads without pushing messages */
|
|
MULTIFD_SYNC_LOCAL,
|
|
/*
|
|
* Sync not only on the sender threads, but also push MULTIFD_FLAG_SYNC
|
|
* message to the wire for each iochannel (which is for a remote sync).
|
|
*
|
|
* When remote sync is used, need to be paired with a follow up
|
|
* RAM_SAVE_FLAG_EOS / RAM_SAVE_FLAG_MULTIFD_FLUSH message on the main
|
|
* channel.
|
|
*/
|
|
MULTIFD_SYNC_ALL,
|
|
} MultiFDSyncReq;
|
|
|
|
bool multifd_send_setup(void);
|
|
void multifd_send_shutdown(void);
|
|
void multifd_send_channel_created(void);
|
|
int multifd_recv_setup(Error **errp);
|
|
void multifd_recv_cleanup(void);
|
|
void multifd_recv_shutdown(void);
|
|
bool multifd_recv_all_channels_created(void);
|
|
bool multifd_recv_new_channel(QIOChannel *ioc, Error **errp);
|
|
void multifd_recv_sync_main(void);
|
|
int multifd_send_sync_main(MultiFDSyncReq req);
|
|
bool multifd_queue_page(RAMBlock *block, ram_addr_t offset);
|
|
bool multifd_recv(void);
|
|
MultiFDRecvData *multifd_get_recv_data(void);
|
|
|
|
/* Multiple fd's */
|
|
|
|
#define MULTIFD_MAGIC 0x11223344U
|
|
#define MULTIFD_VERSION 1
|
|
|
|
/* Multifd Compression flags */
|
|
#define MULTIFD_FLAG_SYNC (1 << 0)
|
|
|
|
/* We reserve 5 bits for compression methods */
|
|
#define MULTIFD_FLAG_COMPRESSION_MASK (0x1f << 1)
|
|
/* we need to be compatible. Before compression value was 0 */
|
|
#define MULTIFD_FLAG_NOCOMP (0 << 1)
|
|
#define MULTIFD_FLAG_ZLIB (1 << 1)
|
|
#define MULTIFD_FLAG_ZSTD (2 << 1)
|
|
#define MULTIFD_FLAG_QPL (4 << 1)
|
|
#define MULTIFD_FLAG_UADK (8 << 1)
|
|
#define MULTIFD_FLAG_QATZIP (16 << 1)
|
|
|
|
/*
|
|
* If set it means that this packet contains device state
|
|
* (MultiFDPacketDeviceState_t), not RAM data (MultiFDPacket_t).
|
|
*/
|
|
#define MULTIFD_FLAG_DEVICE_STATE (32 << 1)
|
|
|
|
/* This value needs to be a multiple of qemu_target_page_size() */
|
|
#define MULTIFD_PACKET_SIZE (512 * 1024)
|
|
|
|
typedef struct {
|
|
uint32_t magic;
|
|
uint32_t version;
|
|
uint32_t flags;
|
|
} __attribute__((packed)) MultiFDPacketHdr_t;
|
|
|
|
typedef struct {
|
|
MultiFDPacketHdr_t hdr;
|
|
|
|
/* maximum number of allocated pages */
|
|
uint32_t pages_alloc;
|
|
/* non zero pages */
|
|
uint32_t normal_pages;
|
|
/* size of the next packet that contains pages */
|
|
uint32_t next_packet_size;
|
|
uint64_t packet_num;
|
|
/* zero pages */
|
|
uint32_t zero_pages;
|
|
uint32_t unused32[1]; /* Reserved for future use */
|
|
uint64_t unused64[3]; /* Reserved for future use */
|
|
char ramblock[256];
|
|
/*
|
|
* This array contains the pointers to:
|
|
* - normal pages (initial normal_pages entries)
|
|
* - zero pages (following zero_pages entries)
|
|
*/
|
|
uint64_t offset[];
|
|
} __attribute__((packed)) MultiFDPacket_t;
|
|
|
|
typedef struct {
|
|
MultiFDPacketHdr_t hdr;
|
|
|
|
char idstr[256];
|
|
uint32_t instance_id;
|
|
|
|
/* size of the next packet that contains the actual data */
|
|
uint32_t next_packet_size;
|
|
} __attribute__((packed)) MultiFDPacketDeviceState_t;
|
|
|
|
typedef struct {
|
|
/* number of used pages */
|
|
uint32_t num;
|
|
/* number of normal pages */
|
|
uint32_t normal_num;
|
|
/*
|
|
* Pointer to the ramblock. NOTE: it's caller's responsibility to make
|
|
* sure the pointer is always valid!
|
|
*/
|
|
RAMBlock *block;
|
|
/* offset array of each page, managed by multifd */
|
|
ram_addr_t *offset;
|
|
} MultiFDPages_t;
|
|
|
|
struct MultiFDRecvData {
|
|
void *opaque;
|
|
size_t size;
|
|
/* for preadv */
|
|
off_t file_offset;
|
|
};
|
|
|
|
typedef struct {
|
|
char *idstr;
|
|
uint32_t instance_id;
|
|
char *buf;
|
|
size_t buf_len;
|
|
} MultiFDDeviceState_t;
|
|
|
|
typedef enum {
|
|
MULTIFD_PAYLOAD_NONE,
|
|
MULTIFD_PAYLOAD_RAM,
|
|
MULTIFD_PAYLOAD_DEVICE_STATE,
|
|
} MultiFDPayloadType;
|
|
|
|
typedef struct MultiFDPayload {
|
|
MultiFDPages_t ram;
|
|
MultiFDDeviceState_t device_state;
|
|
} MultiFDPayload;
|
|
|
|
struct MultiFDSendData {
|
|
MultiFDPayloadType type;
|
|
MultiFDPayload u;
|
|
};
|
|
|
|
static inline bool multifd_payload_empty(MultiFDSendData *data)
|
|
{
|
|
return data->type == MULTIFD_PAYLOAD_NONE;
|
|
}
|
|
|
|
static inline bool multifd_payload_device_state(MultiFDSendData *data)
|
|
{
|
|
return data->type == MULTIFD_PAYLOAD_DEVICE_STATE;
|
|
}
|
|
|
|
static inline void multifd_set_payload_type(MultiFDSendData *data,
|
|
MultiFDPayloadType type)
|
|
{
|
|
assert(multifd_payload_empty(data));
|
|
assert(type != MULTIFD_PAYLOAD_NONE);
|
|
|
|
data->type = type;
|
|
}
|
|
|
|
typedef struct {
|
|
/* Fields are only written at creating/deletion time */
|
|
/* No lock required for them, they are read only */
|
|
|
|
/* channel number */
|
|
uint8_t id;
|
|
/* channel thread name */
|
|
char *name;
|
|
/* channel thread id */
|
|
QemuThread thread;
|
|
bool thread_created;
|
|
QemuThread tls_thread;
|
|
bool tls_thread_created;
|
|
/* communication channel */
|
|
QIOChannel *c;
|
|
/* packet allocated len */
|
|
uint32_t packet_len;
|
|
/* multifd flags for sending ram */
|
|
int write_flags;
|
|
|
|
/* sem where to wait for more work */
|
|
QemuSemaphore sem;
|
|
/* syncs main thread and channels */
|
|
QemuSemaphore sem_sync;
|
|
|
|
/* multifd flags for each packet */
|
|
uint32_t flags;
|
|
/*
|
|
* The sender thread has work to do if either of below field is set.
|
|
*
|
|
* @pending_job: a job is pending
|
|
* @pending_sync: a sync request is pending
|
|
*
|
|
* For both of these fields, they're only set by the requesters, and
|
|
* cleared by the multifd sender threads.
|
|
*/
|
|
bool pending_job;
|
|
MultiFDSyncReq pending_sync;
|
|
|
|
MultiFDSendData *data;
|
|
|
|
/* thread local variables. No locking required */
|
|
|
|
/* pointers to the possible packet types */
|
|
MultiFDPacket_t *packet;
|
|
MultiFDPacketDeviceState_t *packet_device_state;
|
|
/* size of the next packet that contains pages */
|
|
uint32_t next_packet_size;
|
|
/* packets sent through this channel */
|
|
uint64_t packets_sent;
|
|
/* buffers to send */
|
|
struct iovec *iov;
|
|
/* number of iovs used */
|
|
uint32_t iovs_num;
|
|
/* used for compression methods */
|
|
void *compress_data;
|
|
} MultiFDSendParams;
|
|
|
|
typedef struct {
|
|
/* Fields are only written at creating/deletion time */
|
|
/* No lock required for them, they are read only */
|
|
|
|
/* channel number */
|
|
uint8_t id;
|
|
/* channel thread name */
|
|
char *name;
|
|
/* channel thread id */
|
|
QemuThread thread;
|
|
bool thread_created;
|
|
/* communication channel */
|
|
QIOChannel *c;
|
|
/* packet allocated len */
|
|
uint32_t packet_len;
|
|
|
|
/* syncs main thread and channels */
|
|
QemuSemaphore sem_sync;
|
|
/* sem where to wait for more work */
|
|
QemuSemaphore sem;
|
|
|
|
/* this mutex protects the following parameters */
|
|
QemuMutex mutex;
|
|
/* should this thread finish */
|
|
bool quit;
|
|
/* multifd flags for each packet */
|
|
uint32_t flags;
|
|
/* global number of generated multifd packets */
|
|
uint64_t packet_num;
|
|
int pending_job;
|
|
MultiFDRecvData *data;
|
|
|
|
/* thread local variables. No locking required */
|
|
|
|
/* pointers to the possible packet types */
|
|
MultiFDPacket_t *packet;
|
|
MultiFDPacketDeviceState_t *packet_dev_state;
|
|
/* size of the next packet that contains pages */
|
|
uint32_t next_packet_size;
|
|
/* packets received through this channel */
|
|
uint64_t packets_recved;
|
|
/* ramblock */
|
|
RAMBlock *block;
|
|
/*
|
|
* Normally, it points to ramblock's host address. When COLO
|
|
* is enabled, it points to the mirror cache for the ramblock.
|
|
*/
|
|
uint8_t *host;
|
|
/* buffers to recv */
|
|
struct iovec *iov;
|
|
/* Pages that are not zero */
|
|
ram_addr_t *normal;
|
|
/* num of non zero pages */
|
|
uint32_t normal_num;
|
|
/* Pages that are zero */
|
|
ram_addr_t *zero;
|
|
/* num of zero pages */
|
|
uint32_t zero_num;
|
|
/* used for de-compression methods */
|
|
void *compress_data;
|
|
/* Flags for the QIOChannel */
|
|
int read_flags;
|
|
} MultiFDRecvParams;
|
|
|
|
typedef struct {
|
|
/*
|
|
* The send_setup, send_cleanup, send_prepare are only called on
|
|
* the QEMU instance at the migration source.
|
|
*/
|
|
|
|
/*
|
|
* Setup for sending side. Called once per channel during channel
|
|
* setup phase.
|
|
*
|
|
* Must allocate p->iov. If packets are in use (default), one
|
|
* extra iovec must be allocated for the packet header. Any memory
|
|
* allocated in this hook must be released at send_cleanup.
|
|
*
|
|
* p->write_flags may be used for passing flags to the QIOChannel.
|
|
*
|
|
* p->compression_data may be used by compression methods to store
|
|
* compression data.
|
|
*/
|
|
int (*send_setup)(MultiFDSendParams *p, Error **errp);
|
|
|
|
/*
|
|
* Cleanup for sending side. Called once per channel during
|
|
* channel cleanup phase.
|
|
*/
|
|
void (*send_cleanup)(MultiFDSendParams *p, Error **errp);
|
|
|
|
/*
|
|
* Prepare the send packet. Called as a result of multifd_send()
|
|
* on the client side, with p pointing to the MultiFDSendParams of
|
|
* a channel that is currently idle.
|
|
*
|
|
* Must populate p->iov with the data to be sent, increment
|
|
* p->iovs_num to match the amount of iovecs used and set
|
|
* p->next_packet_size with the amount of data currently present
|
|
* in p->iov.
|
|
*
|
|
* Must indicate whether this is a compression packet by setting
|
|
* p->flags.
|
|
*
|
|
* As a last step, if packets are in use (default), must prepare
|
|
* the packet by calling multifd_send_fill_packet().
|
|
*/
|
|
int (*send_prepare)(MultiFDSendParams *p, Error **errp);
|
|
|
|
/*
|
|
* The recv_setup, recv_cleanup, recv are only called on the QEMU
|
|
* instance at the migration destination.
|
|
*/
|
|
|
|
/*
|
|
* Setup for receiving side. Called once per channel during
|
|
* channel setup phase. May be empty.
|
|
*
|
|
* May allocate data structures for the receiving of data. May use
|
|
* p->iov. Compression methods may use p->compress_data.
|
|
*/
|
|
int (*recv_setup)(MultiFDRecvParams *p, Error **errp);
|
|
|
|
/*
|
|
* Cleanup for receiving side. Called once per channel during
|
|
* channel cleanup phase. May be empty.
|
|
*/
|
|
void (*recv_cleanup)(MultiFDRecvParams *p);
|
|
|
|
/*
|
|
* Data receive method. Called as a result of multifd_recv() on
|
|
* the client side, with p pointing to the MultiFDRecvParams of a
|
|
* channel that is currently idle. Only called if there is data
|
|
* available to receive.
|
|
*
|
|
* Must validate p->flags according to what was set at
|
|
* send_prepare.
|
|
*
|
|
* Must read the data from the QIOChannel p->c.
|
|
*/
|
|
int (*recv)(MultiFDRecvParams *p, Error **errp);
|
|
} MultiFDMethods;
|
|
|
|
void multifd_register_ops(int method, const MultiFDMethods *ops);
|
|
void multifd_send_fill_packet(MultiFDSendParams *p);
|
|
bool multifd_send_prepare_common(MultiFDSendParams *p);
|
|
void multifd_send_zero_page_detect(MultiFDSendParams *p);
|
|
void multifd_recv_zero_page_process(MultiFDRecvParams *p);
|
|
|
|
void multifd_channel_connect(MultiFDSendParams *p, QIOChannel *ioc);
|
|
bool multifd_send(MultiFDSendData **send_data);
|
|
MultiFDSendData *multifd_send_data_alloc(void);
|
|
void multifd_send_data_clear(MultiFDSendData *data);
|
|
void multifd_send_data_free(MultiFDSendData *data);
|
|
|
|
static inline uint32_t multifd_ram_page_size(void)
|
|
{
|
|
return qemu_target_page_size();
|
|
}
|
|
|
|
static inline uint32_t multifd_ram_page_count(void)
|
|
{
|
|
return MULTIFD_PACKET_SIZE / qemu_target_page_size();
|
|
}
|
|
|
|
void multifd_ram_save_setup(void);
|
|
void multifd_ram_save_cleanup(void);
|
|
int multifd_ram_flush_and_sync(QEMUFile *f);
|
|
bool multifd_ram_sync_per_round(void);
|
|
bool multifd_ram_sync_per_section(void);
|
|
void multifd_ram_payload_alloc(MultiFDPages_t *pages);
|
|
void multifd_ram_payload_free(MultiFDPages_t *pages);
|
|
void multifd_ram_fill_packet(MultiFDSendParams *p);
|
|
int multifd_ram_unfill_packet(MultiFDRecvParams *p, Error **errp);
|
|
|
|
void multifd_send_data_clear_device_state(MultiFDDeviceState_t *device_state);
|
|
|
|
void multifd_device_state_send_setup(void);
|
|
void multifd_device_state_send_cleanup(void);
|
|
|
|
void multifd_device_state_send_prepare(MultiFDSendParams *p);
|
|
|
|
#endif
|