400 lines
12 KiB
Diff
400 lines
12 KiB
Diff
|
|
From b6d1e022b7bb06faf2dcad3062b7061b59ef68a9 Mon Sep 17 00:00:00 2001
|
||
|
|
From: =?UTF-8?q?Hyman=20Huang=28=E9=BB=84=E5=8B=87=29?=
|
||
|
|
<huangy81@chinatelecom.cn>
|
||
|
|
Date: Sun, 26 Jun 2022 01:38:32 +0800
|
||
|
|
Subject: [PATCH 3/3] migration/dirtyrate: Refactor dirty page rate calculation
|
||
|
|
MIME-Version: 1.0
|
||
|
|
Content-Type: text/plain; charset=UTF-8
|
||
|
|
Content-Transfer-Encoding: 8bit
|
||
|
|
|
||
|
|
abstract out dirty log change logic into function
|
||
|
|
global_dirty_log_change.
|
||
|
|
|
||
|
|
abstract out dirty page rate calculation logic via
|
||
|
|
dirty-ring into function vcpu_calculate_dirtyrate.
|
||
|
|
|
||
|
|
abstract out mathematical dirty page rate calculation
|
||
|
|
into do_calculate_dirtyrate, decouple it from DirtyStat.
|
||
|
|
|
||
|
|
rename set_sample_page_period to dirty_stat_wait, which
|
||
|
|
is well-understood and will be reused in dirtylimit.
|
||
|
|
|
||
|
|
handle cpu hotplug/unplug scenario during measurement of
|
||
|
|
dirty page rate.
|
||
|
|
|
||
|
|
export util functions outside migration.
|
||
|
|
|
||
|
|
Signed-off-by: Hyman Huang(黄勇) <huangy81@chinatelecom.cn>
|
||
|
|
Reviewed-by: Peter Xu <peterx@redhat.com>
|
||
|
|
Message-Id: <7b6f6f4748d5b3d017b31a0429e630229ae97538.1656177590.git.huangy81@chinatelecom.cn>
|
||
|
|
Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
|
||
|
|
---
|
||
|
|
include/sysemu/dirtyrate.h | 28 +++++
|
||
|
|
migration/dirtyrate.c | 227 +++++++++++++++++++++++--------------
|
||
|
|
migration/dirtyrate.h | 7 +-
|
||
|
|
3 files changed, 174 insertions(+), 88 deletions(-)
|
||
|
|
create mode 100644 include/sysemu/dirtyrate.h
|
||
|
|
|
||
|
|
diff --git a/include/sysemu/dirtyrate.h b/include/sysemu/dirtyrate.h
|
||
|
|
new file mode 100644
|
||
|
|
index 0000000000..4d3b9a4902
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/include/sysemu/dirtyrate.h
|
||
|
|
@@ -0,0 +1,28 @@
|
||
|
|
+/*
|
||
|
|
+ * dirty page rate helper functions
|
||
|
|
+ *
|
||
|
|
+ * Copyright (c) 2022 CHINA TELECOM CO.,LTD.
|
||
|
|
+ *
|
||
|
|
+ * Authors:
|
||
|
|
+ * Hyman Huang(黄勇) <huangy81@chinatelecom.cn>
|
||
|
|
+ *
|
||
|
|
+ * 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_DIRTYRATE_H
|
||
|
|
+#define QEMU_DIRTYRATE_H
|
||
|
|
+
|
||
|
|
+typedef struct VcpuStat {
|
||
|
|
+ int nvcpu; /* number of vcpu */
|
||
|
|
+ DirtyRateVcpu *rates; /* array of dirty rate for each vcpu */
|
||
|
|
+} VcpuStat;
|
||
|
|
+
|
||
|
|
+int64_t vcpu_calculate_dirtyrate(int64_t calc_time_ms,
|
||
|
|
+ VcpuStat *stat,
|
||
|
|
+ unsigned int flag,
|
||
|
|
+ bool one_shot);
|
||
|
|
+
|
||
|
|
+void global_dirty_log_change(unsigned int flag,
|
||
|
|
+ bool start);
|
||
|
|
+#endif
|
||
|
|
diff --git a/migration/dirtyrate.c b/migration/dirtyrate.c
|
||
|
|
index 8043bc7946..c449095fc3 100644
|
||
|
|
--- a/migration/dirtyrate.c
|
||
|
|
+++ b/migration/dirtyrate.c
|
||
|
|
@@ -46,7 +46,7 @@ static struct DirtyRateStat DirtyStat;
|
||
|
|
static DirtyRateMeasureMode dirtyrate_mode =
|
||
|
|
DIRTY_RATE_MEASURE_MODE_PAGE_SAMPLING;
|
||
|
|
|
||
|
|
-static int64_t set_sample_page_period(int64_t msec, int64_t initial_time)
|
||
|
|
+static int64_t dirty_stat_wait(int64_t msec, int64_t initial_time)
|
||
|
|
{
|
||
|
|
int64_t current_time;
|
||
|
|
|
||
|
|
@@ -60,6 +60,132 @@ static int64_t set_sample_page_period(int64_t msec, int64_t initial_time)
|
||
|
|
return msec;
|
||
|
|
}
|
||
|
|
|
||
|
|
+static inline void record_dirtypages(DirtyPageRecord *dirty_pages,
|
||
|
|
+ CPUState *cpu, bool start)
|
||
|
|
+{
|
||
|
|
+ if (start) {
|
||
|
|
+ dirty_pages[cpu->cpu_index].start_pages = cpu->dirty_pages;
|
||
|
|
+ } else {
|
||
|
|
+ dirty_pages[cpu->cpu_index].end_pages = cpu->dirty_pages;
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static int64_t do_calculate_dirtyrate(DirtyPageRecord dirty_pages,
|
||
|
|
+ int64_t calc_time_ms)
|
||
|
|
+{
|
||
|
|
+ uint64_t memory_size_MB;
|
||
|
|
+ uint64_t increased_dirty_pages =
|
||
|
|
+ dirty_pages.end_pages - dirty_pages.start_pages;
|
||
|
|
+
|
||
|
|
+ memory_size_MB = (increased_dirty_pages * TARGET_PAGE_SIZE) >> 20;
|
||
|
|
+
|
||
|
|
+ return memory_size_MB * 1000 / calc_time_ms;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+void global_dirty_log_change(unsigned int flag, bool start)
|
||
|
|
+{
|
||
|
|
+ qemu_mutex_lock_iothread();
|
||
|
|
+ if (start) {
|
||
|
|
+ memory_global_dirty_log_start(flag);
|
||
|
|
+ } else {
|
||
|
|
+ memory_global_dirty_log_stop(flag);
|
||
|
|
+ }
|
||
|
|
+ qemu_mutex_unlock_iothread();
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/*
|
||
|
|
+ * global_dirty_log_sync
|
||
|
|
+ * 1. sync dirty log from kvm
|
||
|
|
+ * 2. stop dirty tracking if needed.
|
||
|
|
+ */
|
||
|
|
+static void global_dirty_log_sync(unsigned int flag, bool one_shot)
|
||
|
|
+{
|
||
|
|
+ qemu_mutex_lock_iothread();
|
||
|
|
+ memory_global_dirty_log_sync();
|
||
|
|
+ if (one_shot) {
|
||
|
|
+ memory_global_dirty_log_stop(flag);
|
||
|
|
+ }
|
||
|
|
+ qemu_mutex_unlock_iothread();
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static DirtyPageRecord *vcpu_dirty_stat_alloc(VcpuStat *stat)
|
||
|
|
+{
|
||
|
|
+ CPUState *cpu;
|
||
|
|
+ DirtyPageRecord *records;
|
||
|
|
+ int nvcpu = 0;
|
||
|
|
+
|
||
|
|
+ CPU_FOREACH(cpu) {
|
||
|
|
+ nvcpu++;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ stat->nvcpu = nvcpu;
|
||
|
|
+ stat->rates = g_malloc0(sizeof(DirtyRateVcpu) * nvcpu);
|
||
|
|
+
|
||
|
|
+ records = g_malloc0(sizeof(DirtyPageRecord) * nvcpu);
|
||
|
|
+
|
||
|
|
+ return records;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+static void vcpu_dirty_stat_collect(VcpuStat *stat,
|
||
|
|
+ DirtyPageRecord *records,
|
||
|
|
+ bool start)
|
||
|
|
+{
|
||
|
|
+ CPUState *cpu;
|
||
|
|
+
|
||
|
|
+ CPU_FOREACH(cpu) {
|
||
|
|
+ record_dirtypages(records, cpu, start);
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+int64_t vcpu_calculate_dirtyrate(int64_t calc_time_ms,
|
||
|
|
+ VcpuStat *stat,
|
||
|
|
+ unsigned int flag,
|
||
|
|
+ bool one_shot)
|
||
|
|
+{
|
||
|
|
+ DirtyPageRecord *records;
|
||
|
|
+ int64_t init_time_ms;
|
||
|
|
+ int64_t duration;
|
||
|
|
+ int64_t dirtyrate;
|
||
|
|
+ int i = 0;
|
||
|
|
+ unsigned int gen_id;
|
||
|
|
+
|
||
|
|
+retry:
|
||
|
|
+ init_time_ms = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
|
||
|
|
+
|
||
|
|
+ cpu_list_lock();
|
||
|
|
+ gen_id = cpu_list_generation_id_get();
|
||
|
|
+ records = vcpu_dirty_stat_alloc(stat);
|
||
|
|
+ vcpu_dirty_stat_collect(stat, records, true);
|
||
|
|
+ cpu_list_unlock();
|
||
|
|
+
|
||
|
|
+ duration = dirty_stat_wait(calc_time_ms, init_time_ms);
|
||
|
|
+
|
||
|
|
+ global_dirty_log_sync(flag, one_shot);
|
||
|
|
+
|
||
|
|
+ cpu_list_lock();
|
||
|
|
+ if (gen_id != cpu_list_generation_id_get()) {
|
||
|
|
+ g_free(records);
|
||
|
|
+ g_free(stat->rates);
|
||
|
|
+ cpu_list_unlock();
|
||
|
|
+ goto retry;
|
||
|
|
+ }
|
||
|
|
+ vcpu_dirty_stat_collect(stat, records, false);
|
||
|
|
+ cpu_list_unlock();
|
||
|
|
+
|
||
|
|
+ for (i = 0; i < stat->nvcpu; i++) {
|
||
|
|
+ dirtyrate = do_calculate_dirtyrate(records[i], duration);
|
||
|
|
+
|
||
|
|
+ stat->rates[i].id = i;
|
||
|
|
+ stat->rates[i].dirty_rate = dirtyrate;
|
||
|
|
+
|
||
|
|
+ trace_dirtyrate_do_calculate_vcpu(i, dirtyrate);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ g_free(records);
|
||
|
|
+
|
||
|
|
+ return duration;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
static bool is_sample_period_valid(int64_t sec)
|
||
|
|
{
|
||
|
|
if (sec < MIN_FETCH_DIRTYRATE_TIME_SEC ||
|
||
|
|
@@ -396,44 +522,6 @@ static bool compare_page_hash_info(struct RamblockDirtyInfo *info,
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
-static inline void record_dirtypages(DirtyPageRecord *dirty_pages,
|
||
|
|
- CPUState *cpu, bool start)
|
||
|
|
-{
|
||
|
|
- if (start) {
|
||
|
|
- dirty_pages[cpu->cpu_index].start_pages = cpu->dirty_pages;
|
||
|
|
- } else {
|
||
|
|
- dirty_pages[cpu->cpu_index].end_pages = cpu->dirty_pages;
|
||
|
|
- }
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-static void dirtyrate_global_dirty_log_start(void)
|
||
|
|
-{
|
||
|
|
- qemu_mutex_lock_iothread();
|
||
|
|
- memory_global_dirty_log_start(GLOBAL_DIRTY_DIRTY_RATE);
|
||
|
|
- qemu_mutex_unlock_iothread();
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-static void dirtyrate_global_dirty_log_stop(void)
|
||
|
|
-{
|
||
|
|
- qemu_mutex_lock_iothread();
|
||
|
|
- memory_global_dirty_log_sync();
|
||
|
|
- memory_global_dirty_log_stop(GLOBAL_DIRTY_DIRTY_RATE);
|
||
|
|
- qemu_mutex_unlock_iothread();
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
-static int64_t do_calculate_dirtyrate_vcpu(DirtyPageRecord dirty_pages)
|
||
|
|
-{
|
||
|
|
- uint64_t memory_size_MB;
|
||
|
|
- int64_t time_s;
|
||
|
|
- uint64_t increased_dirty_pages =
|
||
|
|
- dirty_pages.end_pages - dirty_pages.start_pages;
|
||
|
|
-
|
||
|
|
- memory_size_MB = (increased_dirty_pages * TARGET_PAGE_SIZE) >> 20;
|
||
|
|
- time_s = DirtyStat.calc_time;
|
||
|
|
-
|
||
|
|
- return memory_size_MB / time_s;
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
static inline void record_dirtypages_bitmap(DirtyPageRecord *dirty_pages,
|
||
|
|
bool start)
|
||
|
|
{
|
||
|
|
@@ -444,11 +532,6 @@ static inline void record_dirtypages_bitmap(DirtyPageRecord *dirty_pages,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
-static void do_calculate_dirtyrate_bitmap(DirtyPageRecord dirty_pages)
|
||
|
|
-{
|
||
|
|
- DirtyStat.dirty_rate = do_calculate_dirtyrate_vcpu(dirty_pages);
|
||
|
|
-}
|
||
|
|
-
|
||
|
|
static inline void dirtyrate_manual_reset_protect(void)
|
||
|
|
{
|
||
|
|
RAMBlock *block = NULL;
|
||
|
|
@@ -492,71 +575,49 @@ static void calculate_dirtyrate_dirty_bitmap(struct DirtyRateConfig config)
|
||
|
|
DirtyStat.start_time = start_time / 1000;
|
||
|
|
|
||
|
|
msec = config.sample_period_seconds * 1000;
|
||
|
|
- msec = set_sample_page_period(msec, start_time);
|
||
|
|
+ msec = dirty_stat_wait(msec, start_time);
|
||
|
|
DirtyStat.calc_time = msec / 1000;
|
||
|
|
|
||
|
|
/*
|
||
|
|
- * dirtyrate_global_dirty_log_stop do two things.
|
||
|
|
+ * do two things.
|
||
|
|
* 1. fetch dirty bitmap from kvm
|
||
|
|
* 2. stop dirty tracking
|
||
|
|
*/
|
||
|
|
- dirtyrate_global_dirty_log_stop();
|
||
|
|
+ global_dirty_log_sync(GLOBAL_DIRTY_DIRTY_RATE, true);
|
||
|
|
|
||
|
|
record_dirtypages_bitmap(&dirty_pages, false);
|
||
|
|
|
||
|
|
- do_calculate_dirtyrate_bitmap(dirty_pages);
|
||
|
|
+ DirtyStat.dirty_rate = do_calculate_dirtyrate(dirty_pages, msec);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void calculate_dirtyrate_dirty_ring(struct DirtyRateConfig config)
|
||
|
|
{
|
||
|
|
- CPUState *cpu;
|
||
|
|
- int64_t msec = 0;
|
||
|
|
- int64_t start_time;
|
||
|
|
+ int64_t duration;
|
||
|
|
uint64_t dirtyrate = 0;
|
||
|
|
uint64_t dirtyrate_sum = 0;
|
||
|
|
- DirtyPageRecord *dirty_pages;
|
||
|
|
- int nvcpu = 0;
|
||
|
|
int i = 0;
|
||
|
|
|
||
|
|
- CPU_FOREACH(cpu) {
|
||
|
|
- nvcpu++;
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- dirty_pages = g_new(DirtyPageRecord, nvcpu);
|
||
|
|
-
|
||
|
|
- DirtyStat.dirty_ring.nvcpu = nvcpu;
|
||
|
|
- DirtyStat.dirty_ring.rates = g_new(DirtyRateVcpu, nvcpu);
|
||
|
|
-
|
||
|
|
- dirtyrate_global_dirty_log_start();
|
||
|
|
-
|
||
|
|
- CPU_FOREACH(cpu) {
|
||
|
|
- record_dirtypages(dirty_pages, cpu, true);
|
||
|
|
- }
|
||
|
|
-
|
||
|
|
- start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
|
||
|
|
- DirtyStat.start_time = start_time / 1000;
|
||
|
|
+ /* start log sync */
|
||
|
|
+ global_dirty_log_change(GLOBAL_DIRTY_DIRTY_RATE, true);
|
||
|
|
|
||
|
|
- msec = config.sample_period_seconds * 1000;
|
||
|
|
- msec = set_sample_page_period(msec, start_time);
|
||
|
|
- DirtyStat.calc_time = msec / 1000;
|
||
|
|
+ DirtyStat.start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) / 1000;
|
||
|
|
|
||
|
|
- dirtyrate_global_dirty_log_stop();
|
||
|
|
+ /* calculate vcpu dirtyrate */
|
||
|
|
+ duration = vcpu_calculate_dirtyrate(config.sample_period_seconds * 1000,
|
||
|
|
+ &DirtyStat.dirty_ring,
|
||
|
|
+ GLOBAL_DIRTY_DIRTY_RATE,
|
||
|
|
+ true);
|
||
|
|
|
||
|
|
- CPU_FOREACH(cpu) {
|
||
|
|
- record_dirtypages(dirty_pages, cpu, false);
|
||
|
|
- }
|
||
|
|
+ DirtyStat.calc_time = duration / 1000;
|
||
|
|
|
||
|
|
+ /* calculate vm dirtyrate */
|
||
|
|
for (i = 0; i < DirtyStat.dirty_ring.nvcpu; i++) {
|
||
|
|
- dirtyrate = do_calculate_dirtyrate_vcpu(dirty_pages[i]);
|
||
|
|
- trace_dirtyrate_do_calculate_vcpu(i, dirtyrate);
|
||
|
|
-
|
||
|
|
- DirtyStat.dirty_ring.rates[i].id = i;
|
||
|
|
+ dirtyrate = DirtyStat.dirty_ring.rates[i].dirty_rate;
|
||
|
|
DirtyStat.dirty_ring.rates[i].dirty_rate = dirtyrate;
|
||
|
|
dirtyrate_sum += dirtyrate;
|
||
|
|
}
|
||
|
|
|
||
|
|
DirtyStat.dirty_rate = dirtyrate_sum;
|
||
|
|
- g_free(dirty_pages);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void calculate_dirtyrate_sample_vm(struct DirtyRateConfig config)
|
||
|
|
@@ -574,7 +635,7 @@ static void calculate_dirtyrate_sample_vm(struct DirtyRateConfig config)
|
||
|
|
rcu_read_unlock();
|
||
|
|
|
||
|
|
msec = config.sample_period_seconds * 1000;
|
||
|
|
- msec = set_sample_page_period(msec, initial_time);
|
||
|
|
+ msec = dirty_stat_wait(msec, initial_time);
|
||
|
|
DirtyStat.start_time = initial_time / 1000;
|
||
|
|
DirtyStat.calc_time = msec / 1000;
|
||
|
|
|
||
|
|
diff --git a/migration/dirtyrate.h b/migration/dirtyrate.h
|
||
|
|
index 69d4c5b865..594a5c0bb6 100644
|
||
|
|
--- a/migration/dirtyrate.h
|
||
|
|
+++ b/migration/dirtyrate.h
|
||
|
|
@@ -13,6 +13,8 @@
|
||
|
|
#ifndef QEMU_MIGRATION_DIRTYRATE_H
|
||
|
|
#define QEMU_MIGRATION_DIRTYRATE_H
|
||
|
|
|
||
|
|
+#include "sysemu/dirtyrate.h"
|
||
|
|
+
|
||
|
|
/*
|
||
|
|
* Sample 512 pages per GB as default.
|
||
|
|
*/
|
||
|
|
@@ -65,11 +67,6 @@ typedef struct SampleVMStat {
|
||
|
|
uint64_t total_block_mem_MB; /* size of total sampled pages in MB */
|
||
|
|
} SampleVMStat;
|
||
|
|
|
||
|
|
-typedef struct VcpuStat {
|
||
|
|
- int nvcpu; /* number of vcpu */
|
||
|
|
- DirtyRateVcpu *rates; /* array of dirty rate for each vcpu */
|
||
|
|
-} VcpuStat;
|
||
|
|
-
|
||
|
|
/*
|
||
|
|
* Store calculation statistics for each measure.
|
||
|
|
*/
|
||
|
|
--
|
||
|
|
2.27.0
|
||
|
|
|