Collect number of dirty pages for progresseively increasing time periods starting with 125ms up to number of seconds specified with calc-dirty-rate. Report through qmp and hmp: 1) vector of dirty page measurements, 2) page size, 3) total number of VM pages, 4) number of sampled pages.
Signed-off-by: Andrei Gudkov <gudkov.and...@huawei.com> --- migration/dirtyrate.c | 165 +++++++++++++++++++++++++++++------------- migration/dirtyrate.h | 25 ++++++- qapi/migration.json | 24 +++++- 3 files changed, 160 insertions(+), 54 deletions(-) diff --git a/migration/dirtyrate.c b/migration/dirtyrate.c index acba3213a3..4491bbe91a 100644 --- a/migration/dirtyrate.c +++ b/migration/dirtyrate.c @@ -224,6 +224,7 @@ static struct DirtyRateInfo *query_dirty_rate_info(void) info->calc_time = DirtyStat.calc_time; info->sample_pages = DirtyStat.sample_pages; info->mode = dirtyrate_mode; + info->page_size = TARGET_PAGE_SIZE; if (qatomic_read(&CalculatingState) == DIRTY_RATE_STATUS_MEASURED) { info->has_dirty_rate = true; @@ -245,6 +246,29 @@ static struct DirtyRateInfo *query_dirty_rate_info(void) info->vcpu_dirty_rate = head; } + if (dirtyrate_mode == DIRTY_RATE_MEASURE_MODE_PAGE_SAMPLING) { + int64List *periods_head = NULL; + int64List **periods_tail = &periods_head; + int64List *n_dirty_pages_head = NULL; + int64List **n_dirty_pages_tail = &n_dirty_pages_head; + + info->n_total_pages = DirtyStat.page_sampling.n_total_pages; + info->has_n_total_pages = true; + + info->n_sampled_pages = DirtyStat.page_sampling.n_sampled_pages; + info->has_n_sampled_pages = true; + + for (i = 0; i < DirtyStat.page_sampling.n_readings; i++) { + DirtyReading *dr = &DirtyStat.page_sampling.readings[i]; + QAPI_LIST_APPEND(periods_tail, dr->period); + QAPI_LIST_APPEND(n_dirty_pages_tail, dr->n_dirty_pages); + } + info->n_dirty_pages = n_dirty_pages_head; + info->periods = periods_head; + info->has_n_dirty_pages = true; + info->has_periods = true; + } + if (dirtyrate_mode == DIRTY_RATE_MEASURE_MODE_DIRTY_BITMAP) { info->sample_pages = 0; } @@ -265,9 +289,11 @@ static void init_dirtyrate_stat(int64_t start_time, switch (config.mode) { case DIRTY_RATE_MEASURE_MODE_PAGE_SAMPLING: - DirtyStat.page_sampling.total_dirty_samples = 0; - DirtyStat.page_sampling.total_sample_count = 0; - DirtyStat.page_sampling.total_block_mem_MB = 0; + DirtyStat.page_sampling.n_total_pages = 0; + DirtyStat.page_sampling.n_sampled_pages = 0; + DirtyStat.page_sampling.n_readings = 0; + DirtyStat.page_sampling.readings = g_try_malloc0_n(MAX_DIRTY_READINGS, + sizeof(DirtyReading)); break; case DIRTY_RATE_MEASURE_MODE_DIRTY_RING: DirtyStat.dirty_ring.nvcpu = -1; @@ -285,28 +311,10 @@ static void cleanup_dirtyrate_stat(struct DirtyRateConfig config) free(DirtyStat.dirty_ring.rates); DirtyStat.dirty_ring.rates = NULL; } -} - -static void update_dirtyrate_stat(struct RamblockDirtyInfo *info) -{ - DirtyStat.page_sampling.total_dirty_samples += info->sample_dirty_count; - DirtyStat.page_sampling.total_sample_count += info->sample_pages_count; - /* size of total pages in MB */ - DirtyStat.page_sampling.total_block_mem_MB += (info->ramblock_pages * - TARGET_PAGE_SIZE) >> 20; -} - -static void update_dirtyrate(uint64_t msec) -{ - uint64_t dirtyrate; - uint64_t total_dirty_samples = DirtyStat.page_sampling.total_dirty_samples; - uint64_t total_sample_count = DirtyStat.page_sampling.total_sample_count; - uint64_t total_block_mem_MB = DirtyStat.page_sampling.total_block_mem_MB; - - dirtyrate = total_dirty_samples * total_block_mem_MB * - 1000 / (total_sample_count * msec); - - DirtyStat.dirty_rate = dirtyrate; + if (DirtyStat.page_sampling.readings) { + free(DirtyStat.page_sampling.readings); + DirtyStat.page_sampling.readings = NULL; + } } /* @@ -377,12 +385,14 @@ static bool save_ramblock_hash(struct RamblockDirtyInfo *info) return false; } - rand = g_rand_new(); + rand = g_rand_new(); + DirtyStat.page_sampling.n_total_pages += info->ramblock_pages; for (i = 0; i < sample_pages_count; i++) { info->sample_page_vfn[i] = g_rand_int_range(rand, 0, info->ramblock_pages - 1); info->hash_result[i] = get_ramblock_vfn_hash(info, info->sample_page_vfn[i]); + DirtyStat.page_sampling.n_sampled_pages++; } g_rand_free(rand); @@ -479,18 +489,20 @@ out: return ret; } -static void calc_page_dirty_rate(struct RamblockDirtyInfo *info) +static int64_t calc_page_dirty_rate(struct RamblockDirtyInfo *info) { uint32_t hash; int i; + int64_t n_dirty = 0; for (i = 0; i < info->sample_pages_count; i++) { hash = get_ramblock_vfn_hash(info, info->sample_page_vfn[i]); if (hash != info->hash_result[i]) { + n_dirty++; trace_calc_page_dirty_rate(info->idstr, hash, info->hash_result[i]); - info->sample_dirty_count++; } } + return n_dirty; } static struct RamblockDirtyInfo * @@ -519,11 +531,12 @@ find_block_matched(RAMBlock *block, int count, return &infos[i]; } -static bool compare_page_hash_info(struct RamblockDirtyInfo *info, +static int64_t compare_page_hash_info(struct RamblockDirtyInfo *info, int block_count) { struct RamblockDirtyInfo *block_dinfo = NULL; RAMBlock *block = NULL; + int64_t n_dirty = 0; RAMBLOCK_FOREACH_MIGRATABLE(block) { if (skip_sample_ramblock(block)) { @@ -533,15 +546,10 @@ static bool compare_page_hash_info(struct RamblockDirtyInfo *info, if (block_dinfo == NULL) { continue; } - calc_page_dirty_rate(block_dinfo); - update_dirtyrate_stat(block_dinfo); - } - - if (DirtyStat.page_sampling.total_sample_count == 0) { - return false; + n_dirty += calc_page_dirty_rate(block_dinfo); } - return true; + return n_dirty; } static inline void record_dirtypages_bitmap(DirtyPageRecord *dirty_pages, @@ -642,34 +650,77 @@ static void calculate_dirtyrate_dirty_ring(struct DirtyRateConfig config) DirtyStat.dirty_rate = dirtyrate_sum; } +static int64_t increase_period(int64_t prev_period, int64_t max_period) +{ + int64_t delta; + int64_t next_period; + + if (prev_period < 500) { + delta = 125; + } else if (prev_period < 1000) { + delta = 250; + } else if (prev_period < 2000) { + delta = 500; + } else if (prev_period < 4000) { + delta = 1000; + } else if (prev_period < 10000) { + delta = 2000; + } else { + delta = 5000; + } + + next_period = prev_period + delta; + if (next_period + delta >= max_period) { + next_period = max_period; + } + return next_period; +} + + static void calculate_dirtyrate_sample_vm(struct DirtyRateConfig config) { struct RamblockDirtyInfo *block_dinfo = NULL; int block_count = 0; - int64_t msec = 0; int64_t initial_time; + int64_t current_time; + /* initial pass */ rcu_read_lock(); initial_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); - if (!record_ramblock_hash_info(&block_dinfo, config, &block_count)) { + bool ok = record_ramblock_hash_info(&block_dinfo, config, &block_count); + rcu_read_unlock(); + if ((!ok) || (DirtyStat.page_sampling.n_sampled_pages == 0)) { goto out; } - rcu_read_unlock(); - msec = config.sample_period_seconds * 1000; - msec = dirty_stat_wait(msec, initial_time); - DirtyStat.start_time = initial_time / 1000; - DirtyStat.calc_time = msec / 1000; + int64_t period = INITIAL_PERIOD_MS; + while (true) { + current_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); + int64_t delta = initial_time + period - current_time; + if (delta > 0) { + g_usleep(delta * 1000); + } - rcu_read_lock(); - if (!compare_page_hash_info(block_dinfo, block_count)) { - goto out; - } + rcu_read_lock(); + current_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); + int64_t n_dirty = compare_page_hash_info(block_dinfo, block_count); + rcu_read_unlock(); - update_dirtyrate(msec); + SampleVMStat *ps = &DirtyStat.page_sampling; + ps->readings[ps->n_readings].period = current_time - initial_time; + ps->readings[ps->n_readings].n_dirty_pages = n_dirty; + ps->n_readings++; + + if (period >= DirtyStat.calc_time * 1000) { + int64_t mb_total = (ps->n_total_pages * TARGET_PAGE_SIZE) >> 20; + int64_t mb_dirty = n_dirty * mb_total / ps->n_sampled_pages; + DirtyStat.dirty_rate = mb_dirty * 1000 / period; + break; + } + period = increase_period(period, DirtyStat.calc_time * 1000); + } out: - rcu_read_unlock(); free_ramblock_dirty_info(block_dinfo, block_count); } @@ -836,7 +887,23 @@ void hmp_info_dirty_rate(Monitor *mon, const QDict *qdict) monitor_printf(mon, "(not ready)\n"); } + if (info->has_n_total_pages) { + monitor_printf(mon, "Page count (page size %d):\n", TARGET_PAGE_SIZE); + monitor_printf(mon, " Total: %"PRIi64"\n", info->n_total_pages); + monitor_printf(mon, " Sampled: %"PRIi64"\n", info->n_sampled_pages); + int64List *periods = info->periods; + int64List *n_dirty_pages = info->n_dirty_pages; + while (periods) { + monitor_printf(mon, " Dirty(%"PRIi64"ms): %"PRIi64"\n", + periods->value, n_dirty_pages->value); + periods = periods->next; + n_dirty_pages = n_dirty_pages->next; + } + } + qapi_free_DirtyRateVcpuList(info->vcpu_dirty_rate); + qapi_free_int64List(info->periods); + qapi_free_int64List(info->n_dirty_pages); g_free(info); } diff --git a/migration/dirtyrate.h b/migration/dirtyrate.h index 594a5c0bb6..7a97e2b076 100644 --- a/migration/dirtyrate.h +++ b/migration/dirtyrate.h @@ -42,6 +42,18 @@ #define MIN_SAMPLE_PAGE_COUNT 128 #define MAX_SAMPLE_PAGE_COUNT 16384 +/* + * Initial sampling period expressed in milliseconds + */ +#define INITIAL_PERIOD_MS 125 + +/* + * Upper bound on the number of DirtyReadings calculcated based on + * INITIAL_PERIOD_MS, MAX_FETCH_DIRTYRATE_TIME_SEC and increase_period() + */ +#define MAX_DIRTY_READINGS 32 + + struct DirtyRateConfig { uint64_t sample_pages_per_gigabytes; /* sample pages per GB */ int64_t sample_period_seconds; /* time duration between two sampling */ @@ -57,14 +69,19 @@ struct RamblockDirtyInfo { uint64_t ramblock_pages; /* ramblock size in TARGET_PAGE_SIZE */ uint64_t *sample_page_vfn; /* relative offset address for sampled page */ uint64_t sample_pages_count; /* count of sampled pages */ - uint64_t sample_dirty_count; /* count of dirty pages we measure */ uint32_t *hash_result; /* array of hash result for sampled pages */ }; +typedef struct DirtyReading { + int64_t period; /* time period in milliseconds */ + int64_t n_dirty_pages; /* number of observed dirty pages */ +} DirtyReading; + typedef struct SampleVMStat { - uint64_t total_dirty_samples; /* total dirty sampled page */ - uint64_t total_sample_count; /* total sampled pages */ - uint64_t total_block_mem_MB; /* size of total sampled pages in MB */ + int64_t n_total_pages; /* total number of pages */ + int64_t n_sampled_pages; /* number of sampled pages */ + int64_t n_readings; + DirtyReading *readings; } SampleVMStat; /* diff --git a/qapi/migration.json b/qapi/migration.json index 2c35b7b9cf..f818f51e0e 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -1805,6 +1805,22 @@ # @vcpu-dirty-rate: dirtyrate for each vcpu if dirty-ring # mode specified (Since 6.2) # +# @page-size: page size in bytes (since 8.1) +# +# @n-total-pages: [page-sampling] total number of VM pages (since 8.1) +# +# @n-sampled-pages: [page-sampling] number of sampled VM pages (since 8.1) +# +# @periods: [page-sampling] array of time periods expressed in milliseconds +# for which dirty-sample measurements were collected (since 8.1) +# +# @n-dirty-pages: [page-sampling] number of pages among all sampled pages +# that were observed as changed during respective time period. +# i-th element of this array corresponds to the i-th element +# of the @periods array, i.e. @n-dirty-pages[i] is the number +# of dirtied pages during period of @periods[i] milliseconds +# after the initiation of calc-dirty-rate (since 8.1) +# # Since: 5.2 ## { 'struct': 'DirtyRateInfo', @@ -1814,7 +1830,13 @@ 'calc-time': 'int64', 'sample-pages': 'uint64', 'mode': 'DirtyRateMeasureMode', - '*vcpu-dirty-rate': [ 'DirtyRateVcpu' ] } } + '*vcpu-dirty-rate': [ 'DirtyRateVcpu' ], + 'page-size': 'int64', + '*n-total-pages': 'int64', + '*n-sampled-pages': 'int64', + '*periods': ['int64'], + '*n-dirty-pages': ['int64'] } } + ## # @calc-dirty-rate: -- 2.30.2