+++ /dev/null
-From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
-From: Stefan Reiter <s.reiter@proxmox.com>
-Date: Wed, 19 Aug 2020 17:02:00 +0200
-Subject: [PATCH] PVE: add query-pbs-bitmap-info QMP call
-
-Returns advanced information about dirty bitmaps used (or not used) for
-the latest PBS backup.
-
-Signed-off-by: Stefan Reiter <s.reiter@proxmox.com>
-Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
----
- monitor/hmp-cmds.c | 28 ++++++-----
- pve-backup.c | 117 ++++++++++++++++++++++++++++++++-----------
- qapi/block-core.json | 56 +++++++++++++++++++++
- 3 files changed, 159 insertions(+), 42 deletions(-)
-
-diff --git a/monitor/hmp-cmds.c b/monitor/hmp-cmds.c
-index c5c74ac1dc..df273f41fb 100644
---- a/monitor/hmp-cmds.c
-+++ b/monitor/hmp-cmds.c
-@@ -198,6 +198,7 @@ void hmp_info_mice(Monitor *mon, const QDict *qdict)
- void hmp_info_backup(Monitor *mon, const QDict *qdict)
- {
- BackupStatus *info;
-+ PBSBitmapInfoList *bitmap_info;
-
- info = qmp_query_backup(NULL);
-
-@@ -228,26 +229,29 @@ void hmp_info_backup(Monitor *mon, const QDict *qdict)
- // this should not happen normally
- monitor_printf(mon, "Total size: %d\n", 0);
- } else {
-- bool incremental = false;
- size_t total_or_dirty = info->total;
-- if (info->has_transferred) {
-- if (info->has_dirty && info->dirty) {
-- if (info->dirty < info->total) {
-- total_or_dirty = info->dirty;
-- incremental = true;
-- }
-- }
-+ bitmap_info = qmp_query_pbs_bitmap_info(NULL);
-+
-+ while (bitmap_info) {
-+ monitor_printf(mon, "Drive %s:\n",
-+ bitmap_info->value->drive);
-+ monitor_printf(mon, " bitmap action: %s\n",
-+ PBSBitmapAction_str(bitmap_info->value->action));
-+ monitor_printf(mon, " size: %zd\n",
-+ bitmap_info->value->size);
-+ monitor_printf(mon, " dirty: %zd\n",
-+ bitmap_info->value->dirty);
-+ bitmap_info = bitmap_info->next;
- }
-
-- int per = (info->transferred * 100)/total_or_dirty;
--
-- monitor_printf(mon, "Backup mode: %s\n", incremental ? "incremental" : "full");
-+ qapi_free_PBSBitmapInfoList(bitmap_info);
-
- int zero_per = (info->has_zero_bytes && info->zero_bytes) ?
- (info->zero_bytes * 100)/info->total : 0;
- monitor_printf(mon, "Total size: %zd\n", info->total);
-+ int trans_per = (info->transferred * 100)/total_or_dirty;
- monitor_printf(mon, "Transferred bytes: %zd (%d%%)\n",
-- info->transferred, per);
-+ info->transferred, trans_per);
- monitor_printf(mon, "Zero bytes: %zd (%d%%)\n",
- info->zero_bytes, zero_per);
-
-diff --git a/pve-backup.c b/pve-backup.c
-index 4684789813..f90abaa50a 100644
---- a/pve-backup.c
-+++ b/pve-backup.c
-@@ -46,6 +46,7 @@ static struct PVEBackupState {
- size_t transferred;
- size_t reused;
- size_t zero_bytes;
-+ GList *bitmap_list;
- } stat;
- int64_t speed;
- VmaWriter *vmaw;
-@@ -672,7 +673,6 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
- }
-
- size_t total = 0;
-- size_t dirty = 0;
-
- l = di_list;
- while (l) {
-@@ -693,18 +693,33 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
-
- uuid_generate(uuid);
-
-+ qemu_mutex_lock(&backup_state.stat.lock);
-+ backup_state.stat.reused = 0;
-+
-+ /* clear previous backup's bitmap_list */
-+ if (backup_state.stat.bitmap_list) {
-+ GList *bl = backup_state.stat.bitmap_list;
-+ while (bl) {
-+ g_free(((PBSBitmapInfo *)bl->data)->drive);
-+ g_free(bl->data);
-+ bl = g_list_next(bl);
-+ }
-+ g_list_free(backup_state.stat.bitmap_list);
-+ backup_state.stat.bitmap_list = NULL;
-+ }
-+
- if (format == BACKUP_FORMAT_PBS) {
- if (!task->has_password) {
- error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'password'");
-- goto err;
-+ goto err_mutex;
- }
- if (!task->has_backup_id) {
- error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-id'");
-- goto err;
-+ goto err_mutex;
- }
- if (!task->has_backup_time) {
- error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "missing parameter 'backup-time'");
-- goto err;
-+ goto err_mutex;
- }
-
- int dump_cb_block_size = PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE; // Hardcoded (4M)
-@@ -731,12 +746,12 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
- error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
- "proxmox_backup_new failed: %s", pbs_err);
- proxmox_backup_free_error(pbs_err);
-- goto err;
-+ goto err_mutex;
- }
-
- int connect_result = proxmox_backup_co_connect(pbs, task->errp);
- if (connect_result < 0)
-- goto err;
-+ goto err_mutex;
-
- /* register all devices */
- l = di_list;
-@@ -747,6 +762,8 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
- di->block_size = dump_cb_block_size;
-
- const char *devname = bdrv_get_device_name(di->bs);
-+ PBSBitmapAction action = PBS_BITMAP_ACTION_NOT_USED;
-+ size_t dirty = di->size;
-
- BdrvDirtyBitmap *bitmap = bdrv_find_dirty_bitmap(di->bs, PBS_BITMAP_NAME);
- bool expect_only_dirty = false;
-@@ -755,49 +772,59 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
- if (bitmap == NULL) {
- bitmap = bdrv_create_dirty_bitmap(di->bs, dump_cb_block_size, PBS_BITMAP_NAME, task->errp);
- if (!bitmap) {
-- goto err;
-+ goto err_mutex;
- }
-+ action = PBS_BITMAP_ACTION_NEW;
- } else {
- expect_only_dirty = proxmox_backup_check_incremental(pbs, devname, di->size) != 0;
- }
-
- if (expect_only_dirty) {
-- dirty += bdrv_get_dirty_count(bitmap);
-+ /* track clean chunks as reused */
-+ dirty = MIN(bdrv_get_dirty_count(bitmap), di->size);
-+ backup_state.stat.reused += di->size - dirty;
-+ action = PBS_BITMAP_ACTION_USED;
- } else {
- /* mark entire bitmap as dirty to make full backup */
- bdrv_set_dirty_bitmap(bitmap, 0, di->size);
-- dirty += di->size;
-+ if (action != PBS_BITMAP_ACTION_NEW) {
-+ action = PBS_BITMAP_ACTION_INVALID;
-+ }
- }
- di->bitmap = bitmap;
- } else {
-- dirty += di->size;
--
- /* after a full backup the old dirty bitmap is invalid anyway */
- if (bitmap != NULL) {
- bdrv_release_dirty_bitmap(bitmap);
-+ action = PBS_BITMAP_ACTION_NOT_USED_REMOVED;
- }
- }
-
- int dev_id = proxmox_backup_co_register_image(pbs, devname, di->size, expect_only_dirty, task->errp);
- if (dev_id < 0) {
-- goto err;
-+ goto err_mutex;
- }
-
- if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, task->errp))) {
-- goto err;
-+ goto err_mutex;
- }
-
- di->dev_id = dev_id;
-+
-+ PBSBitmapInfo *info = g_malloc(sizeof(*info));
-+ info->drive = g_strdup(devname);
-+ info->action = action;
-+ info->size = di->size;
-+ info->dirty = dirty;
-+ backup_state.stat.bitmap_list = g_list_append(backup_state.stat.bitmap_list, info);
- }
- } else if (format == BACKUP_FORMAT_VMA) {
-- dirty = total;
--
- vmaw = vma_writer_create(task->backup_file, uuid, &local_err);
- if (!vmaw) {
- if (local_err) {
- error_propagate(task->errp, local_err);
- }
-- goto err;
-+ goto err_mutex;
- }
-
- /* register all devices for vma writer */
-@@ -807,7 +834,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
- l = g_list_next(l);
-
- if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, task->errp))) {
-- goto err;
-+ goto err_mutex;
- }
-
- const char *devname = bdrv_get_device_name(di->bs);
-@@ -815,16 +842,14 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
- if (di->dev_id <= 0) {
- error_set(task->errp, ERROR_CLASS_GENERIC_ERROR,
- "register_stream failed");
-- goto err;
-+ goto err_mutex;
- }
- }
- } else if (format == BACKUP_FORMAT_DIR) {
-- dirty = total;
--
- if (mkdir(task->backup_file, 0640) != 0) {
- error_setg_errno(task->errp, errno, "can't create directory '%s'\n",
- task->backup_file);
-- goto err;
-+ goto err_mutex;
- }
- backup_dir = task->backup_file;
-
-@@ -841,18 +866,18 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
- di->size, flags, false, &local_err);
- if (local_err) {
- error_propagate(task->errp, local_err);
-- goto err;
-+ goto err_mutex;
- }
-
- di->target = bdrv_open(di->targetfile, NULL, NULL, flags, &local_err);
- if (!di->target) {
- error_propagate(task->errp, local_err);
-- goto err;
-+ goto err_mutex;
- }
- }
- } else {
- error_set(task->errp, ERROR_CLASS_GENERIC_ERROR, "unknown backup format");
-- goto err;
-+ goto err_mutex;
- }
-
-
-@@ -860,7 +885,7 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
- if (task->has_config_file) {
- if (pvebackup_co_add_config(task->config_file, config_name, format, backup_dir,
- vmaw, pbs, task->errp) != 0) {
-- goto err;
-+ goto err_mutex;
- }
- }
-
-@@ -868,12 +893,11 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
- if (task->has_firewall_file) {
- if (pvebackup_co_add_config(task->firewall_file, firewall_name, format, backup_dir,
- vmaw, pbs, task->errp) != 0) {
-- goto err;
-+ goto err_mutex;
- }
- }
- /* initialize global backup_state now */
--
-- qemu_mutex_lock(&backup_state.stat.lock);
-+ /* note: 'reused' and 'bitmap_list' are initialized earlier */
-
- if (backup_state.stat.error) {
- error_free(backup_state.stat.error);
-@@ -893,10 +917,9 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
- char *uuid_str = g_strdup(backup_state.stat.uuid_str);
-
- backup_state.stat.total = total;
-- backup_state.stat.dirty = dirty;
-+ backup_state.stat.dirty = total - backup_state.stat.reused;
- backup_state.stat.transferred = 0;
- backup_state.stat.zero_bytes = 0;
-- backup_state.stat.reused = format == BACKUP_FORMAT_PBS && dirty >= total ? 0 : total - dirty;
-
- qemu_mutex_unlock(&backup_state.stat.lock);
-
-@@ -913,6 +936,9 @@ static void coroutine_fn pvebackup_co_prepare(void *opaque)
- task->result = uuid_info;
- return;
-
-+err_mutex:
-+ qemu_mutex_unlock(&backup_state.stat.lock);
-+
- err:
-
- l = di_list;
-@@ -1076,11 +1102,42 @@ BackupStatus *qmp_query_backup(Error **errp)
- return info;
- }
-
-+PBSBitmapInfoList *qmp_query_pbs_bitmap_info(Error **errp)
-+{
-+ PBSBitmapInfoList *head = NULL, **p_next = &head;
-+
-+ qemu_mutex_lock(&backup_state.stat.lock);
-+
-+ GList *l = backup_state.stat.bitmap_list;
-+ while (l) {
-+ PBSBitmapInfo *info = (PBSBitmapInfo *)l->data;
-+ l = g_list_next(l);
-+
-+ /* clone bitmap info to avoid auto free after QMP marshalling */
-+ PBSBitmapInfo *info_ret = g_malloc0(sizeof(*info_ret));
-+ info_ret->drive = g_strdup(info->drive);
-+ info_ret->action = info->action;
-+ info_ret->size = info->size;
-+ info_ret->dirty = info->dirty;
-+
-+ PBSBitmapInfoList *info_list = g_malloc0(sizeof(*info_list));
-+ info_list->value = info_ret;
-+
-+ *p_next = info_list;
-+ p_next = &info_list->next;
-+ }
-+
-+ qemu_mutex_unlock(&backup_state.stat.lock);
-+
-+ return head;
-+}
-+
- ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp)
- {
- ProxmoxSupportStatus *ret = g_malloc0(sizeof(*ret));
- ret->pbs_library_version = g_strdup(proxmox_backup_qemu_version());
- ret->pbs_dirty_bitmap = true;
- ret->pbs_dirty_bitmap_savevm = true;
-+ ret->query_bitmap_info = true;
- return ret;
- }
-diff --git a/qapi/block-core.json b/qapi/block-core.json
-index 7d25aa4fa1..69571d86eb 100644
---- a/qapi/block-core.json
-+++ b/qapi/block-core.json
-@@ -875,6 +875,8 @@
- # @pbs-dirty-bitmap: True if dirty-bitmap-incremental backups to PBS are
- # supported.
- #
-+# @query-bitmap-info: True if the 'query-pbs-bitmap-info' QMP call is supported.
-+#
- # @pbs-dirty-bitmap-savevm: True if 'dirty-bitmaps' migration capability can
- # safely be set for savevm-async.
- #
-@@ -883,6 +885,7 @@
- ##
- { 'struct': 'ProxmoxSupportStatus',
- 'data': { 'pbs-dirty-bitmap': 'bool',
-+ 'query-bitmap-info': 'bool',
- 'pbs-dirty-bitmap-savevm': 'bool',
- 'pbs-library-version': 'str' } }
-
-@@ -896,6 +899,59 @@
- ##
- { 'command': 'query-proxmox-support', 'returns': 'ProxmoxSupportStatus' }
-
-+##
-+# @PBSBitmapAction:
-+#
-+# An action taken on a dirty-bitmap when a backup job was started.
-+#
-+# @not-used: Bitmap mode was not enabled.
-+#
-+# @not-used-removed: Bitmap mode was not enabled, but a bitmap from a
-+# previous backup still existed and was removed.
-+#
-+# @new: A new bitmap was attached to the drive for this backup.
-+#
-+# @used: An existing bitmap will be used to only backup changed data.
-+#
-+# @invalid: A bitmap existed, but had to be cleared since it's associated
-+# base snapshot did not match the base given for the current job or
-+# the crypt mode has changed.
-+#
-+##
-+{ 'enum': 'PBSBitmapAction',
-+ 'data': ['not-used', 'not-used-removed', 'new', 'used', 'invalid'] }
-+
-+##
-+# @PBSBitmapInfo:
-+#
-+# Contains information about dirty bitmaps used for each drive in a PBS backup.
-+#
-+# @drive: The underlying drive.
-+#
-+# @action: The action that was taken when the backup started.
-+#
-+# @size: The total size of the drive.
-+#
-+# @dirty: How much of the drive is considered dirty and will be backed up,
-+# or 'size' if everything will be.
-+#
-+##
-+{ 'struct': 'PBSBitmapInfo',
-+ 'data': { 'drive': 'str', 'action': 'PBSBitmapAction', 'size': 'int',
-+ 'dirty': 'int' } }
-+
-+##
-+# @query-pbs-bitmap-info:
-+#
-+# Returns information about dirty bitmaps used on the most recently started
-+# backup. Returns nothing when the last backup was not using PBS or if no
-+# backup occured in this session.
-+#
-+# Returns: @PBSBitmapInfo
-+#
-+##
-+{ 'command': 'query-pbs-bitmap-info', 'returns': ['PBSBitmapInfo'] }
-+
- ##
- # @BlockDeviceTimedStats:
- #