From nobody Sat May 11 19:56:56 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=nongnu.org ARC-Seal: i=1; a=rsa-sha256; t=1682599458; cv=none; d=zohomail.com; s=zohoarc; b=eW0R52wcJjGWO5a0hJz/rEI3JQrpmelNt5t0yXxU/gS5M5uaX22CSS0ihwUH86n/oD4/TNOUR5d37V+6+K8u3qmxFDaZ0dhOI6KfMNP7wJJlvlLBCkWMHPiH03HQ6EilsOq9CnakZxqO1q4fKqbsvBVxDsUaQEMSpNnc7Z9DeoU= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1682599458; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:References:Sender:Subject:To; bh=oe5xyKBjWMefovUVNP/zPQTF6PGG1XWuV/bhhnmXtOY=; b=kMSf561QyEcBMP/uy8fexRpIsbshZ0A5kPN5SRjp86Ga7V1lMNyXOj+IDXbJ31ClRBlnaHwXsWcde3eX31VaXT4h/MY7MKkwmK5DtP6X5PSVHSaH01yqrDurXiMb1y+bOfrTXl2cVUGXiUeHMoH48WmVveaE7JvRpKWtUh5ASOo= ARC-Authentication-Results: i=1; mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1682599458305708.6299991398329; Thu, 27 Apr 2023 05:44:18 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1ps0yo-0006o1-Lm; Thu, 27 Apr 2023 08:44:11 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ps0ym-0006mY-Ez for qemu-devel@nongnu.org; Thu, 27 Apr 2023 08:44:08 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ps0yZ-0001qR-1I for qemu-devel@nongnu.org; Thu, 27 Apr 2023 08:44:08 -0400 Received: from lhrpeml500004.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4Q6b5H2CmYz6D9gs; Thu, 27 Apr 2023 20:43:47 +0800 (CST) Received: from DESKTOP-0LHM7NF.huawei.com (10.199.58.101) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.23; Thu, 27 Apr 2023 13:43:48 +0100 To: CC: , , , , , Andrei Gudkov Subject: [PATCH v2 1/4] migration/calc-dirty-rate: replaced CRC32 with xxHash Date: Thu, 27 Apr 2023 15:42:57 +0300 Message-ID: X-Mailer: git-send-email 2.30.2 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Originating-IP: [10.199.58.101] X-ClientProxiedBy: dggems703-chm.china.huawei.com (10.3.19.180) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=185.176.79.56; envelope-from=gudkov.andrei@huawei.com; helo=frasgout.his.huawei.com X-Spam_score_int: -18 X-Spam_score: -1.9 X-Spam_bar: - X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-to: Andrei Gudkov From: Andrei Gudkov via Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZM-MESSAGEID: 1682599458623100001 Content-Type: text/plain; charset="utf-8" This significantly reduces overhead of dirty page rate calculation in sampling mode. Tested using 32GiB VM on E5-2690 CPU. With CRC32: total_pages=3D8388608 sampled_pages=3D16384 millis=3D71 With xxHash: total_pages=3D8388608 sampled_pages=3D16384 millis=3D14 Signed-off-by: Andrei Gudkov Reviewed-by: Juan Quintela --- migration/dirtyrate.c | 45 +++++++++++++++++++++++++++++++++--------- migration/trace-events | 4 ++-- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/migration/dirtyrate.c b/migration/dirtyrate.c index 180ba38c7a..acba3213a3 100644 --- a/migration/dirtyrate.c +++ b/migration/dirtyrate.c @@ -29,6 +29,7 @@ #include "sysemu/kvm.h" #include "sysemu/runstate.h" #include "exec/memory.h" +#include "qemu/xxhash.h" =20 /* * total_dirty_pages is procted by BQL and is used @@ -308,6 +309,33 @@ static void update_dirtyrate(uint64_t msec) DirtyStat.dirty_rate =3D dirtyrate; } =20 +/* + * Compute hash of a single page of size TARGET_PAGE_SIZE. + */ +static uint32_t compute_page_hash(void *ptr) +{ + uint32_t i; + uint64_t v1, v2, v3, v4; + uint64_t res; + const uint64_t *p =3D ptr; + + v1 =3D QEMU_XXHASH_SEED + XXH_PRIME64_1 + XXH_PRIME64_2; + v2 =3D QEMU_XXHASH_SEED + XXH_PRIME64_2; + v3 =3D QEMU_XXHASH_SEED + 0; + v4 =3D QEMU_XXHASH_SEED - XXH_PRIME64_1; + for (i =3D 0; i < TARGET_PAGE_SIZE / 8; i +=3D 4) { + v1 =3D XXH64_round(v1, p[i + 0]); + v2 =3D XXH64_round(v2, p[i + 1]); + v3 =3D XXH64_round(v3, p[i + 2]); + v4 =3D XXH64_round(v4, p[i + 3]); + } + res =3D XXH64_mergerounds(v1, v2, v3, v4); + res +=3D TARGET_PAGE_SIZE; + res =3D XXH64_avalanche(res); + return (uint32_t)(res & UINT32_MAX); +} + + /* * get hash result for the sampled memory with length of TARGET_PAGE_SIZE * in ramblock, which starts from ramblock base address. @@ -315,13 +343,12 @@ static void update_dirtyrate(uint64_t msec) static uint32_t get_ramblock_vfn_hash(struct RamblockDirtyInfo *info, uint64_t vfn) { - uint32_t crc; + uint32_t hash; =20 - crc =3D crc32(0, (info->ramblock_addr + - vfn * TARGET_PAGE_SIZE), TARGET_PAGE_SIZE); + hash =3D compute_page_hash(info->ramblock_addr + vfn * TARGET_PAGE_SIZ= E); =20 - trace_get_ramblock_vfn_hash(info->idstr, vfn, crc); - return crc; + trace_get_ramblock_vfn_hash(info->idstr, vfn, hash); + return hash; } =20 static bool save_ramblock_hash(struct RamblockDirtyInfo *info) @@ -454,13 +481,13 @@ out: =20 static void calc_page_dirty_rate(struct RamblockDirtyInfo *info) { - uint32_t crc; + uint32_t hash; int i; =20 for (i =3D 0; i < info->sample_pages_count; i++) { - crc =3D get_ramblock_vfn_hash(info, info->sample_page_vfn[i]); - if (crc !=3D info->hash_result[i]) { - trace_calc_page_dirty_rate(info->idstr, crc, info->hash_result= [i]); + hash =3D get_ramblock_vfn_hash(info, info->sample_page_vfn[i]); + if (hash !=3D info->hash_result[i]) { + trace_calc_page_dirty_rate(info->idstr, hash, info->hash_resul= t[i]); info->sample_dirty_count++; } } diff --git a/migration/trace-events b/migration/trace-events index 92161eeac5..f39818c329 100644 --- a/migration/trace-events +++ b/migration/trace-events @@ -342,8 +342,8 @@ dirty_bitmap_load_success(void) "" # dirtyrate.c dirtyrate_set_state(const char *new_state) "new state %s" query_dirty_rate_info(const char *new_state) "current state %s" -get_ramblock_vfn_hash(const char *idstr, uint64_t vfn, uint32_t crc) "ramb= lock name: %s, vfn: %"PRIu64 ", crc: %" PRIu32 -calc_page_dirty_rate(const char *idstr, uint32_t new_crc, uint32_t old_crc= ) "ramblock name: %s, new crc: %" PRIu32 ", old crc: %" PRIu32 +get_ramblock_vfn_hash(const char *idstr, uint64_t vfn, uint32_t hash) "ram= block name: %s, vfn: %"PRIu64 ", hash: %" PRIu32 +calc_page_dirty_rate(const char *idstr, uint32_t new_hash, uint32_t old_ha= sh) "ramblock name: %s, new hash: %" PRIu32 ", old hash: %" PRIu32 skip_sample_ramblock(const char *idstr, uint64_t ramblock_size) "ramblock = name: %s, ramblock size: %" PRIu64 find_page_matched(const char *idstr) "ramblock %s addr or size changed" dirtyrate_calculate(int64_t dirtyrate) "dirty rate: %" PRIi64 " MB/s" --=20 2.30.2 From nobody Sat May 11 19:56:56 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=nongnu.org ARC-Seal: i=1; a=rsa-sha256; t=1682599488; cv=none; d=zohomail.com; s=zohoarc; b=L0fe1DalqYXl9c/eRyufv/tqkP+jeJyTJizIwJSRct29lgsIIzKricY5q3rqbUeQTHmI4YDPkCOZ/0Otm8GNP2e31s+E0uZAxhygcwPBEYOgHSINOK9lcSy6D9UQnD69xTK00EG2FZ0GOJNZf12z8WIF1kRIAeioQq0wRuDQUPA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1682599488; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:References:Sender:Subject:To; bh=pAGrAAEFCRbAi6DAnDLCgrb1OqQsMmeCxmhOw4nXy9I=; b=ZWtGKFbpaJq7VjJqjNBHjzRzi87FVuOY4dl5612HnmI9KW+d4HchrYB4qjFLWa5Go7qcepsEM2jGdiz1GHlbRgwuFazI08cTJf4TSTdtAecVKJPN+2qzh8j9pf+5XGaus/yQHywg9tFRTiADjfA4rMR7p83gMdljCbymaKJtRA8= ARC-Authentication-Results: i=1; mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1682599488308468.8401091017497; Thu, 27 Apr 2023 05:44:48 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1ps0yk-0006mI-KB; Thu, 27 Apr 2023 08:44:06 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ps0yi-0006ll-Q0 for qemu-devel@nongnu.org; Thu, 27 Apr 2023 08:44:04 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ps0yg-0001sE-Ej for qemu-devel@nongnu.org; Thu, 27 Apr 2023 08:44:04 -0400 Received: from lhrpeml500004.china.huawei.com (unknown [172.18.147.206]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4Q6b1s3j0zz6J8C3; Thu, 27 Apr 2023 20:40:49 +0800 (CST) Received: from DESKTOP-0LHM7NF.huawei.com (10.199.58.101) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.23; Thu, 27 Apr 2023 13:43:56 +0100 To: CC: , , , , , Andrei Gudkov Subject: [PATCH v2 2/4] migration/calc-dirty-rate: detailed stats in sampling mode Date: Thu, 27 Apr 2023 15:42:58 +0300 Message-ID: <22436421241c49c9b6d9b9120d166392c40fb991.1682598010.git.gudkov.andrei@huawei.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Originating-IP: [10.199.58.101] X-ClientProxiedBy: dggems703-chm.china.huawei.com (10.3.19.180) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=185.176.79.56; envelope-from=gudkov.andrei@huawei.com; helo=frasgout.his.huawei.com X-Spam_score_int: -41 X-Spam_score: -4.2 X-Spam_bar: ---- X-Spam_report: (-4.2 / 5.0 requ) BAYES_00=-1.9, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-to: Andrei Gudkov From: Andrei Gudkov via Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZM-MESSAGEID: 1682599488763100001 Content-Type: text/plain; charset="utf-8" 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 --- 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 =3D DirtyStat.calc_time; info->sample_pages =3D DirtyStat.sample_pages; info->mode =3D dirtyrate_mode; + info->page_size =3D TARGET_PAGE_SIZE; =20 if (qatomic_read(&CalculatingState) =3D=3D DIRTY_RATE_STATUS_MEASURED)= { info->has_dirty_rate =3D true; @@ -245,6 +246,29 @@ static struct DirtyRateInfo *query_dirty_rate_info(voi= d) info->vcpu_dirty_rate =3D head; } =20 + if (dirtyrate_mode =3D=3D DIRTY_RATE_MEASURE_MODE_PAGE_SAMPLING) { + int64List *periods_head =3D NULL; + int64List **periods_tail =3D &periods_head; + int64List *n_dirty_pages_head =3D NULL; + int64List **n_dirty_pages_tail =3D &n_dirty_pages_head; + + info->n_total_pages =3D DirtyStat.page_sampling.n_total_pages; + info->has_n_total_pages =3D true; + + info->n_sampled_pages =3D DirtyStat.page_sampling.n_sampled_pa= ges; + info->has_n_sampled_pages =3D true; + + for (i =3D 0; i < DirtyStat.page_sampling.n_readings; i++) { + DirtyReading *dr =3D &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 =3D n_dirty_pages_head; + info->periods =3D periods_head; + info->has_n_dirty_pages =3D true; + info->has_periods =3D true; + } + if (dirtyrate_mode =3D=3D DIRTY_RATE_MEASURE_MODE_DIRTY_BITMAP) { info->sample_pages =3D 0; } @@ -265,9 +289,11 @@ static void init_dirtyrate_stat(int64_t start_time, =20 switch (config.mode) { case DIRTY_RATE_MEASURE_MODE_PAGE_SAMPLING: - DirtyStat.page_sampling.total_dirty_samples =3D 0; - DirtyStat.page_sampling.total_sample_count =3D 0; - DirtyStat.page_sampling.total_block_mem_MB =3D 0; + DirtyStat.page_sampling.n_total_pages =3D 0; + DirtyStat.page_sampling.n_sampled_pages =3D 0; + DirtyStat.page_sampling.n_readings =3D 0; + DirtyStat.page_sampling.readings =3D g_try_malloc0_n(MAX_DIRTY_REA= DINGS, + sizeof(DirtyRead= ing)); break; case DIRTY_RATE_MEASURE_MODE_DIRTY_RING: DirtyStat.dirty_ring.nvcpu =3D -1; @@ -285,28 +311,10 @@ static void cleanup_dirtyrate_stat(struct DirtyRateCo= nfig config) free(DirtyStat.dirty_ring.rates); DirtyStat.dirty_ring.rates =3D NULL; } -} - -static void update_dirtyrate_stat(struct RamblockDirtyInfo *info) -{ - DirtyStat.page_sampling.total_dirty_samples +=3D info->sample_dirty_co= unt; - DirtyStat.page_sampling.total_sample_count +=3D info->sample_pages_cou= nt; - /* size of total pages in MB */ - DirtyStat.page_sampling.total_block_mem_MB +=3D (info->ramblock_pages * - TARGET_PAGE_SIZE) >> 20; -} - -static void update_dirtyrate(uint64_t msec) -{ - uint64_t dirtyrate; - uint64_t total_dirty_samples =3D DirtyStat.page_sampling.total_dirty_s= amples; - uint64_t total_sample_count =3D DirtyStat.page_sampling.total_sample_c= ount; - uint64_t total_block_mem_MB =3D DirtyStat.page_sampling.total_block_me= m_MB; - - dirtyrate =3D total_dirty_samples * total_block_mem_MB * - 1000 / (total_sample_count * msec); - - DirtyStat.dirty_rate =3D dirtyrate; + if (DirtyStat.page_sampling.readings) { + free(DirtyStat.page_sampling.readings); + DirtyStat.page_sampling.readings =3D NULL; + } } =20 /* @@ -377,12 +385,14 @@ static bool save_ramblock_hash(struct RamblockDirtyIn= fo *info) return false; } =20 - rand =3D g_rand_new(); + rand =3D g_rand_new(); + DirtyStat.page_sampling.n_total_pages +=3D info->ramblock_pages; for (i =3D 0; i < sample_pages_count; i++) { info->sample_page_vfn[i] =3D g_rand_int_range(rand, 0, info->ramblock_pages -= 1); info->hash_result[i] =3D get_ramblock_vfn_hash(info, info->sample_page_vfn= [i]); + DirtyStat.page_sampling.n_sampled_pages++; } g_rand_free(rand); =20 @@ -479,18 +489,20 @@ out: return ret; } =20 -static void calc_page_dirty_rate(struct RamblockDirtyInfo *info) +static int64_t calc_page_dirty_rate(struct RamblockDirtyInfo *info) { uint32_t hash; int i; =20 + int64_t n_dirty =3D 0; for (i =3D 0; i < info->sample_pages_count; i++) { hash =3D get_ramblock_vfn_hash(info, info->sample_page_vfn[i]); if (hash !=3D info->hash_result[i]) { + n_dirty++; trace_calc_page_dirty_rate(info->idstr, hash, info->hash_resul= t[i]); - info->sample_dirty_count++; } } + return n_dirty; } =20 static struct RamblockDirtyInfo * @@ -519,11 +531,12 @@ find_block_matched(RAMBlock *block, int count, return &infos[i]; } =20 -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 =3D NULL; RAMBlock *block =3D NULL; + int64_t n_dirty =3D 0; =20 RAMBLOCK_FOREACH_MIGRATABLE(block) { if (skip_sample_ramblock(block)) { @@ -533,15 +546,10 @@ static bool compare_page_hash_info(struct RamblockDir= tyInfo *info, if (block_dinfo =3D=3D NULL) { continue; } - calc_page_dirty_rate(block_dinfo); - update_dirtyrate_stat(block_dinfo); - } - - if (DirtyStat.page_sampling.total_sample_count =3D=3D 0) { - return false; + n_dirty +=3D calc_page_dirty_rate(block_dinfo); } =20 - return true; + return n_dirty; } =20 static inline void record_dirtypages_bitmap(DirtyPageRecord *dirty_pages, @@ -642,34 +650,77 @@ static void calculate_dirtyrate_dirty_ring(struct Dir= tyRateConfig config) DirtyStat.dirty_rate =3D dirtyrate_sum; } =20 +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 =3D 125; + } else if (prev_period < 1000) { + delta =3D 250; + } else if (prev_period < 2000) { + delta =3D 500; + } else if (prev_period < 4000) { + delta =3D 1000; + } else if (prev_period < 10000) { + delta =3D 2000; + } else { + delta =3D 5000; + } + + next_period =3D prev_period + delta; + if (next_period + delta >=3D max_period) { + next_period =3D max_period; + } + return next_period; +} + + static void calculate_dirtyrate_sample_vm(struct DirtyRateConfig config) { struct RamblockDirtyInfo *block_dinfo =3D NULL; int block_count =3D 0; - int64_t msec =3D 0; int64_t initial_time; + int64_t current_time; =20 + /* initial pass */ rcu_read_lock(); initial_time =3D qemu_clock_get_ms(QEMU_CLOCK_REALTIME); - if (!record_ramblock_hash_info(&block_dinfo, config, &block_count)) { + bool ok =3D record_ramblock_hash_info(&block_dinfo, config, &block_cou= nt); + rcu_read_unlock(); + if ((!ok) || (DirtyStat.page_sampling.n_sampled_pages =3D=3D 0)) { goto out; } - rcu_read_unlock(); =20 - msec =3D config.sample_period_seconds * 1000; - msec =3D dirty_stat_wait(msec, initial_time); - DirtyStat.start_time =3D initial_time / 1000; - DirtyStat.calc_time =3D msec / 1000; + int64_t period =3D INITIAL_PERIOD_MS; + while (true) { + current_time =3D qemu_clock_get_ms(QEMU_CLOCK_REALTIME); + int64_t delta =3D initial_time + period - current_time; + if (delta > 0) { + g_usleep(delta * 1000); + } =20 - rcu_read_lock(); - if (!compare_page_hash_info(block_dinfo, block_count)) { - goto out; - } + rcu_read_lock(); + current_time =3D qemu_clock_get_ms(QEMU_CLOCK_REALTIME); + int64_t n_dirty =3D compare_page_hash_info(block_dinfo, block_coun= t); + rcu_read_unlock(); =20 - update_dirtyrate(msec); + SampleVMStat *ps =3D &DirtyStat.page_sampling; + ps->readings[ps->n_readings].period =3D current_time - initial_tim= e; + ps->readings[ps->n_readings].n_dirty_pages =3D n_dirty; + ps->n_readings++; + + if (period >=3D DirtyStat.calc_time * 1000) { + int64_t mb_total =3D (ps->n_total_pages * TARGET_PAGE_SIZE) >>= 20; + int64_t mb_dirty =3D n_dirty * mb_total / ps->n_sampled_pages; + DirtyStat.dirty_rate =3D mb_dirty * 1000 / period; + break; + } + period =3D increase_period(period, DirtyStat.calc_time * 1000); + } =20 out: - rcu_read_unlock(); free_ramblock_dirty_info(block_dinfo, block_count); } =20 @@ -836,7 +887,23 @@ void hmp_info_dirty_rate(Monitor *mon, const QDict *qd= ict) monitor_printf(mon, "(not ready)\n"); } =20 + if (info->has_n_total_pages) { + monitor_printf(mon, "Page count (page size %d):\n", TARGET_PAGE_SI= ZE); + monitor_printf(mon, " Total: %"PRIi64"\n", info->n_total_pages); + monitor_printf(mon, " Sampled: %"PRIi64"\n", info->n_sampled_page= s); + int64List *periods =3D info->periods; + int64List *n_dirty_pages =3D info->n_dirty_pages; + while (periods) { + monitor_printf(mon, " Dirty(%"PRIi64"ms): %"PRIi64"\n", + periods->value, n_dirty_pages->value); + periods =3D periods->next; + n_dirty_pages =3D 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); } =20 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 =20 +/* + * 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 */ }; =20 +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; =20 /* 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 per= iod. +# i-th element of this array corresponds to the i-th eleme= nt +# of the @periods array, i.e. @n-dirty-pages[i] is the num= ber +# of dirtied pages during period of @periods[i] millisecon= ds +# 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'] } } + =20 ## # @calc-dirty-rate: --=20 2.30.2 From nobody Sat May 11 19:56:56 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=nongnu.org ARC-Seal: i=1; a=rsa-sha256; t=1682599468; cv=none; d=zohomail.com; s=zohoarc; b=fPqMgFftyMHk+XL5LhLByENPV63gvVeRuU1kW49s5HrVnfWvwT2NO/yrGG+BsmHEhxQPos78/wZlGdog7NkLa6TGhgAF1y7TQCvgbFhxVo0iJsjX8WxXLl0/XReG7bnjUqKh4x876ILump0gfiOq4btWkbYg3ye2Vcag8Cq1bBc= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1682599468; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:References:Sender:Subject:To; bh=dIiJXWTfEfsVcICzCA36PJYpmUn0bK5YPvnw9kPz07k=; b=M3lHKWm19vlhOVMRY9u2O8ChtTddmvI2tv2JJhHupK5jckrGn/rlXOUgfIk5+0nFvFCAlF6Phq6HO9gKmoc8h18wokIx3eNvSMhg+fVOvwLIetk1qMvKK0gyUdevhWrkSZwX033frEmdTlbFx6VkGdtKb8I2b2XGcStzyQQGYcg= ARC-Authentication-Results: i=1; mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1682599468171534.7172989447087; Thu, 27 Apr 2023 05:44:28 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1ps0ys-0006yz-FI; Thu, 27 Apr 2023 08:44:14 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ps0yo-0006nq-KK for qemu-devel@nongnu.org; Thu, 27 Apr 2023 08:44:10 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ps0ym-0001sm-Mj for qemu-devel@nongnu.org; Thu, 27 Apr 2023 08:44:10 -0400 Received: from lhrpeml500004.china.huawei.com (unknown [172.18.147.200]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4Q6b471W3Hz6D9XP; Thu, 27 Apr 2023 20:42:47 +0800 (CST) Received: from DESKTOP-0LHM7NF.huawei.com (10.199.58.101) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.23; Thu, 27 Apr 2023 13:44:03 +0100 To: CC: , , , , , Andrei Gudkov Subject: [PATCH v2 3/4] migration/calc-dirty-rate: added n-zero-pages metric Date: Thu, 27 Apr 2023 15:42:59 +0300 Message-ID: X-Mailer: git-send-email 2.30.2 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Originating-IP: [10.199.58.101] X-ClientProxiedBy: dggems703-chm.china.huawei.com (10.3.19.180) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=185.176.79.56; envelope-from=gudkov.andrei@huawei.com; helo=frasgout.his.huawei.com X-Spam_score_int: -41 X-Spam_score: -4.2 X-Spam_bar: ---- X-Spam_report: (-4.2 / 5.0 requ) BAYES_00=-1.9, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-to: Andrei Gudkov From: Andrei Gudkov via Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZM-MESSAGEID: 1682599469206100001 Content-Type: text/plain; charset="utf-8" In sampling mode, a new metric is collected and reported: number of pages entirely filled with zeroes. Signed-off-by: Andrei Gudkov --- migration/dirtyrate.c | 40 +++++++++++++++++++++++++++++++++++----- migration/dirtyrate.h | 1 + qapi/migration.json | 4 ++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/migration/dirtyrate.c b/migration/dirtyrate.c index 4491bbe91a..55ef69927e 100644 --- a/migration/dirtyrate.c +++ b/migration/dirtyrate.c @@ -258,6 +258,9 @@ static struct DirtyRateInfo *query_dirty_rate_info(void) info->n_sampled_pages =3D DirtyStat.page_sampling.n_sampled_pa= ges; info->has_n_sampled_pages =3D true; =20 + info->n_zero_pages =3D DirtyStat.page_sampling.n_zero_pages; + info->has_n_zero_pages =3D true; + for (i =3D 0; i < DirtyStat.page_sampling.n_readings; i++) { DirtyReading *dr =3D &DirtyStat.page_sampling.readings[i]; QAPI_LIST_APPEND(periods_tail, dr->period); @@ -291,6 +294,7 @@ static void init_dirtyrate_stat(int64_t start_time, case DIRTY_RATE_MEASURE_MODE_PAGE_SAMPLING: DirtyStat.page_sampling.n_total_pages =3D 0; DirtyStat.page_sampling.n_sampled_pages =3D 0; + DirtyStat.page_sampling.n_zero_pages =3D 0; DirtyStat.page_sampling.n_readings =3D 0; DirtyStat.page_sampling.readings =3D g_try_malloc0_n(MAX_DIRTY_REA= DINGS, sizeof(DirtyRead= ing)); @@ -319,6 +323,7 @@ static void cleanup_dirtyrate_stat(struct DirtyRateConf= ig config) =20 /* * Compute hash of a single page of size TARGET_PAGE_SIZE. + * If ptr is NULL, then compute hash of a page entirely filled with zeros. */ static uint32_t compute_page_hash(void *ptr) { @@ -331,11 +336,20 @@ static uint32_t compute_page_hash(void *ptr) v2 =3D QEMU_XXHASH_SEED + XXH_PRIME64_2; v3 =3D QEMU_XXHASH_SEED + 0; v4 =3D QEMU_XXHASH_SEED - XXH_PRIME64_1; - for (i =3D 0; i < TARGET_PAGE_SIZE / 8; i +=3D 4) { - v1 =3D XXH64_round(v1, p[i + 0]); - v2 =3D XXH64_round(v2, p[i + 1]); - v3 =3D XXH64_round(v3, p[i + 2]); - v4 =3D XXH64_round(v4, p[i + 3]); + if (ptr) { + for (i =3D 0; i < TARGET_PAGE_SIZE / 8; i +=3D 4) { + v1 =3D XXH64_round(v1, p[i + 0]); + v2 =3D XXH64_round(v2, p[i + 1]); + v3 =3D XXH64_round(v3, p[i + 2]); + v4 =3D XXH64_round(v4, p[i + 3]); + } + } else { + for (i =3D 0; i < TARGET_PAGE_SIZE / 8; i +=3D 4) { + v1 =3D XXH64_round(v1, 0); + v2 =3D XXH64_round(v2, 0); + v3 =3D XXH64_round(v3, 0); + v4 =3D XXH64_round(v4, 0); + } } res =3D XXH64_mergerounds(v1, v2, v3, v4); res +=3D TARGET_PAGE_SIZE; @@ -343,6 +357,17 @@ static uint32_t compute_page_hash(void *ptr) return (uint32_t)(res & UINT32_MAX); } =20 +static uint32_t get_zero_page_hash(void) +{ + static uint32_t hash; + static int is_computed; + + if (!is_computed) { + hash =3D compute_page_hash(NULL); + is_computed =3D 1; + } + return hash; +} =20 /* * get hash result for the sampled memory with length of TARGET_PAGE_SIZE @@ -364,6 +389,7 @@ static bool save_ramblock_hash(struct RamblockDirtyInfo= *info) unsigned int sample_pages_count; int i; GRand *rand; + uint32_t zero_page_hash =3D get_zero_page_hash(); =20 sample_pages_count =3D info->sample_pages_count; =20 @@ -393,6 +419,9 @@ static bool save_ramblock_hash(struct RamblockDirtyInfo= *info) info->hash_result[i] =3D get_ramblock_vfn_hash(info, info->sample_page_vfn= [i]); DirtyStat.page_sampling.n_sampled_pages++; + if (info->hash_result[i] =3D=3D zero_page_hash) { + DirtyStat.page_sampling.n_zero_pages++; + } } g_rand_free(rand); =20 @@ -891,6 +920,7 @@ void hmp_info_dirty_rate(Monitor *mon, const QDict *qdi= ct) monitor_printf(mon, "Page count (page size %d):\n", TARGET_PAGE_SI= ZE); monitor_printf(mon, " Total: %"PRIi64"\n", info->n_total_pages); monitor_printf(mon, " Sampled: %"PRIi64"\n", info->n_sampled_page= s); + monitor_printf(mon, " Zero: %"PRIi64"\n", info->n_zero_pages); int64List *periods =3D info->periods; int64List *n_dirty_pages =3D info->n_dirty_pages; while (periods) { diff --git a/migration/dirtyrate.h b/migration/dirtyrate.h index 7a97e2b076..e2af72fb8c 100644 --- a/migration/dirtyrate.h +++ b/migration/dirtyrate.h @@ -80,6 +80,7 @@ typedef struct DirtyReading { typedef struct SampleVMStat { int64_t n_total_pages; /* total number of pages */ int64_t n_sampled_pages; /* number of sampled pages */ + int64_t n_zero_pages; /* number of observed zero pages */ int64_t n_readings; DirtyReading *readings; } SampleVMStat; diff --git a/qapi/migration.json b/qapi/migration.json index f818f51e0e..2c48a9ef80 100644 --- a/qapi/migration.json +++ b/qapi/migration.json @@ -1811,6 +1811,9 @@ # # @n-sampled-pages: [page-sampling] number of sampled VM pages (since 8.1) # +# @n-zero-pages: [page-sampling] number of observed all-zero pages among a= ll +# sampled pages (since 8.1) +# # @periods: [page-sampling] array of time periods expressed in milliseconds # for which dirty-sample measurements were collected (since 8.1) # @@ -1834,6 +1837,7 @@ 'page-size': 'int64', '*n-total-pages': 'int64', '*n-sampled-pages': 'int64', + '*n-zero-pages': 'int64', '*periods': ['int64'], '*n-dirty-pages': ['int64'] } } =20 --=20 2.30.2 From nobody Sat May 11 19:56:56 2024 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=nongnu.org ARC-Seal: i=1; a=rsa-sha256; t=1682599489; cv=none; d=zohomail.com; s=zohoarc; b=NiJIk+fJKvVg5tUeT/AKjLEVLUNZr6Ud3OxD8eN8HX2SmE/pqVWU5jxb1Xx7CEBHBGHh3vuDYJJ26E1yWI+xBvE07sBknckwFseB+PphrJro1JfYfqObI+Wi4klU09tHxsk0G0PLCPeJFLM8e3J7uaswGMY5QmZ05ZvD0w3oXYI= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1682599489; h=Content-Type:Content-Transfer-Encoding:Cc:Date:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Reply-To:References:Sender:Subject:To; bh=EEa9G+DC/6tdrWVpeETjuIcvDkn8rApAzUrcQfR9gJQ=; b=n9F+UhHnnvN5Sa4DCx80/mBetFfEaT6ZkXdN+y5efw0mRiQ2F3EY0/jGC6WD1C4o5KnHQGYc3ersSnamlAztDIZD2mqTNsOwCY97Vy9ef6uX6apGH4budiFRNIM4+ZFe+KPsh1KSdqEFDE8z6fy0R0PwiBEnniB0CppByNwW2Q8= ARC-Authentication-Results: i=1; mx.zohomail.com; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1682599489125447.18774248155637; Thu, 27 Apr 2023 05:44:49 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1ps0z3-0007OC-Ii; Thu, 27 Apr 2023 08:44:25 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ps0yy-0007Kp-Rv for qemu-devel@nongnu.org; Thu, 27 Apr 2023 08:44:20 -0400 Received: from frasgout.his.huawei.com ([185.176.79.56]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1ps0yt-0001tb-GB for qemu-devel@nongnu.org; Thu, 27 Apr 2023 08:44:20 -0400 Received: from lhrpeml500004.china.huawei.com (unknown [172.18.147.226]) by frasgout.his.huawei.com (SkyGuard) with ESMTP id 4Q6b245w0Yz6J8CF; Thu, 27 Apr 2023 20:41:00 +0800 (CST) Received: from DESKTOP-0LHM7NF.huawei.com (10.199.58.101) by lhrpeml500004.china.huawei.com (7.191.163.9) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.23; Thu, 27 Apr 2023 13:44:08 +0100 To: CC: , , , , , Andrei Gudkov Subject: [PATCH v2 4/4] migration/calc-dirty-rate: tool to predict migration time Date: Thu, 27 Apr 2023 15:43:00 +0300 Message-ID: <644a9e7f2bff9d36716a3722c729dc88ea40a35a.1682598010.git.gudkov.andrei@huawei.com> X-Mailer: git-send-email 2.30.2 In-Reply-To: References: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-Originating-IP: [10.199.58.101] X-ClientProxiedBy: dggems703-chm.china.huawei.com (10.3.19.180) To lhrpeml500004.china.huawei.com (7.191.163.9) X-CFilter-Loop: Reflected Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=185.176.79.56; envelope-from=gudkov.andrei@huawei.com; helo=frasgout.his.huawei.com X-Spam_score_int: -41 X-Spam_score: -4.2 X-Spam_bar: ---- X-Spam_report: (-4.2 / 5.0 requ) BAYES_00=-1.9, RCVD_IN_DNSWL_MED=-2.3, RCVD_IN_MSPIKE_H2=-0.001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-to: Andrei Gudkov From: Andrei Gudkov via Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZM-MESSAGEID: 1682599490027100005 Signed-off-by: Andrei Gudkov --- MAINTAINERS | 1 + scripts/predict_migration.py | 283 +++++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 scripts/predict_migration.py diff --git a/MAINTAINERS b/MAINTAINERS index fc225e66df..0c578446cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3167,6 +3167,7 @@ F: docs/devel/migration.rst F: qapi/migration.json F: tests/migration/ F: util/userfaultfd.c +F: scripts/predict_migration.py =20 D-Bus M: Marc-Andr=C3=A9 Lureau diff --git a/scripts/predict_migration.py b/scripts/predict_migration.py new file mode 100644 index 0000000000..c92a97585f --- /dev/null +++ b/scripts/predict_migration.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 +# +# Predicts time required to migrate VM under given max downtime constraint. +# +# Copyright (c) 2023 HUAWEI TECHNOLOGIES CO.,LTD. +# +# Authors: +# Andrei Gudkov +# +# This work is licensed under the terms of the GNU GPL, version 2 or +# later. See the COPYING file in the top-level directory. + + +# Usage: +# +# Step 1. Collect dirty page statistics from live VM: +# $ scripts/predict_migration.py calc-dirty-rate >dirt= y.json +# <...takes 1 minute by default...> +# +# Step 2. Run predictor against collected data: +# $ scripts/predict_migration.py predict < dirty.json +# Downtime> | 125ms | 250ms | 500ms | 1000ms | 5000ms | un= lim | +# ------------------------------------------------------------------------= ----- +# 100 Mbps | - | - | - | - | - | 16m= 45s | +# 1 Gbps | - | - | - | - | - | 1m= 39s | +# 2 Gbps | - | - | - | - | 1m55s | = 50s | +# 2.5 Gbps | - | - | - | - | 1m12s | = 40s | +# 5 Gbps | - | - | - | 29s | 25s | = 20s | +# 10 Gbps | 13s | 13s | 12s | 12s | 12s | = 10s | +# 25 Gbps | 5s | 5s | 5s | 5s | 4s | = 4s | +# 40 Gbps | 3s | 3s | 3s | 3s | 3s | = 3s | +# +# The latter prints table that lists estimated time it will take to migrat= e VM. +# This time depends on the network bandwidth and max allowed downtime. +# Dash indicates that migration does not converge. +# Prediction takes care only about migrating RAM and only in pre-copy mode. +# Other features, such as compression or local disk migration, are not sup= ported + + +import sys +import os +import math +import json +from dataclasses import dataclass +import asyncio +import argparse + +sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'python')) +from qemu.qmp import QMPClient + +async def calc_dirty_rate(host, port, calc_time, sample_pages): + client =3D QMPClient() + try: + await client.connect((host, port)) + args =3D { + 'calc-time': calc_time, + 'sample-pages': sample_pages + } + await client.execute('calc-dirty-rate', args) + await asyncio.sleep(calc_time) + while True: + data =3D await client.execute('query-dirty-rate') + if data['status'] =3D=3D 'measuring': + await asyncio.sleep(0.5) + elif data['status'] =3D=3D 'measured': + return data + else: + raise ValueError(data['status']) + finally: + await client.disconnect() + + +class MemoryModel: + """ + Models RAM state during pre-copy migration using calc-dirty-rate resul= ts. + Its primary function is to estimate how many pages will be dirtied + after given time starting from "clean" state. + This function is non-linear and saturates at some point. + """ + + @dataclass + class Point: + period_millis:float + dirty_pages:float + + def __init__(self, data): + """ + :param data: dictionary returned by calc-dirty-rate + """ + self.__points =3D self.__make_points(data) + self.__page_size =3D data['page-size'] + self.__num_total_pages =3D data['n-total-pages'] + self.__num_zero_pages =3D data['n-zero-pages'] / \ + (data['n-sampled-pages'] / data['n-total-pages']) + + def __make_points(self, data): + points =3D list() + + # Add observed points + sample_ratio =3D data['n-sampled-pages'] / data['n-total-pages'] + for millis,dirty_pages in zip(data['periods'], data['n-dirty-pages= ']): + millis =3D float(millis) + dirty_pages =3D dirty_pages / sample_ratio + points.append(MemoryModel.Point(millis, dirty_pages)) + + # Extrapolate function to the left. + # Assuming that the function is convex, the worst case is achieved + # when dirty page count immediately jumps to some value at zero ti= me + # (infinite slope), and next keeps the same slope as in the region + # between the first two observed points: points[0]..points[1] + slope, offset =3D self.__fit_line(points[0], points[1]) + points.insert(0, MemoryModel.Point(0.0, max(offset, 0.0))) + + # Extrapolate function to the right. + # The worst case is achieved when the function has the same slope + # as in the last observed region. + slope, offset =3D self.__fit_line(points[-2], points[-1]) + max_dirty_pages =3D \ + data['n-total-pages'] - (data['n-zero-pages'] / sample_rat= io) + if slope > 0.0: + saturation_millis =3D (max_dirty_pages - offset) / slope + points.append(MemoryModel.Point(saturation_millis, max_dirty_p= ages)) + points.append(MemoryModel.Point(math.inf, max_dirty_pages)) + + return points + + def __fit_line(self, lhs:Point, rhs:Point): + slope =3D (rhs.dirty_pages - lhs.dirty_pages) / \ + (rhs.period_millis - lhs.period_millis) + offset =3D lhs.dirty_pages - slope * lhs.period_millis + return slope, offset + + def page_size(self): + """ + Return page size in bytes + """ + return self.__page_size + + def num_total_pages(self): + return self.__num_total_pages + + def num_zero_pages(self): + """ + Estimated total number of zero pages. Assumed to be constant. + """ + return self.__num_zero_pages + + def num_dirty_pages(self, millis): + """ + Estimate number of dirty pages after given time starting from "cle= an" + state. The estimation is based on piece-wise linear interpolation. + """ + for i in range(len(self.__points)): + if self.__points[i].period_millis =3D=3D millis: + return self.__points[i].dirty_pages + elif self.__points[i].period_millis > millis: + slope, offset =3D self.__fit_line(self.__points[i-1], + self.__points[i]) + return offset + slope * millis + raise RuntimeError("unreachable") + + +def predict_migration_time(model, bandwidth, downtime, deadline=3D3600*100= 0): + """ + Predict how much time it will take to migrate VM under under given + deadline constraint. + + :param model: `MemoryModel` object for a given VM + :param bandwidth: Bandwidth available for migration [bytes/s] + :param downtime: Max allowed downtime [milliseconds] + :param deadline: Max total time to migrate VM before timeout [millisec= onds] + :return: Predicted migration time [milliseconds] or `None` + if migration process doesn't converge before given deadline + """ + + left_zero_pages =3D model.num_zero_pages() + left_normal_pages =3D model.num_total_pages() - model.num_zero_pages() + header_size =3D 8 + + total_millis =3D 0.0 + while True: + iter_bytes =3D 0.0 + iter_bytes +=3D left_normal_pages * (model.page_size() + header_si= ze) + iter_bytes +=3D left_zero_pages * header_size + + iter_millis =3D iter_bytes * 1000.0 / bandwidth + + total_millis +=3D iter_millis + + if iter_millis <=3D downtime: + return int(math.ceil(total_millis)) + elif total_millis > deadline: + return None + else: + left_zero_pages =3D 0 + left_normal_pages =3D model.num_dirty_pages(iter_millis) + + +def run_predict_cmd(model): + @dataclass + class ValStr: + value:object + string:str + + def gbps(value): + return ValStr(value*1024*1024*1024/8, f'{value} Gbps') + + def mbps(value): + return ValStr(value*1024*1024/8, f'{value} Mbps') + + def dt(millis): + if millis is not None: + return ValStr(millis, f'{millis}ms') + else: + return ValStr(math.inf, 'unlim') + + def eta(millis): + if millis is not None: + seconds =3D int(math.ceil(millis/1000.0)) + minutes, seconds =3D divmod(seconds, 60) + s =3D '' + if minutes > 0: + s +=3D f'{minutes}m' + if len(s) > 0: + s +=3D f'{seconds:02d}s' + else: + s +=3D f'{seconds}s' + else: + s =3D '-' + return ValStr(millis, s) + + + bandwidths =3D [mbps(100), gbps(1), gbps(2), gbps(2.5), gbps(5), gbps(= 10), + gbps(25), gbps(40)] + downtimes =3D [dt(125), dt(250), dt(500), dt(1000), dt(5000), dt(None)] + + out =3D '' + out +=3D 'Downtime> |' + for downtime in downtimes: + out +=3D f' {downtime.string:>7} |' + print(out) + + print('-'*len(out)) + + for bandwidth in bandwidths: + print(f'{bandwidth.string:>9} | ', '', end=3D'') + for downtime in downtimes: + millis =3D predict_migration_time(model, + bandwidth.value, + downtime.value) + print(f'{eta(millis).string:>7} | ', '', end=3D'') + print() + +def main(): + parser =3D argparse.ArgumentParser() + subparsers =3D parser.add_subparsers(dest=3D'command', required=3DTrue) + + parser_cdr =3D subparsers.add_parser('calc-dirty-rate', + help=3D'Collect and print dirty page statistics from live VM') + parser_cdr.add_argument('--calc-time', type=3Dint, default=3D60, + help=3D'Calculation time in seconds') + parser_cdr.add_argument('--sample-pages', type=3Dint, default=3D512, + help=3D'Number of sampled pages per one gigabyte of RAM') + parser_cdr.add_argument('host', metavar=3D'host', type=3Dstr, help=3D'= QMP host') + parser_cdr.add_argument('port', metavar=3D'port', type=3Dint, help=3D'= QMP port') + + subparsers.add_parser('predict', help=3D'Predict migration time') + + args =3D parser.parse_args() + + if args.command =3D=3D 'calc-dirty-rate': + data =3D asyncio.run(calc_dirty_rate(host=3Dargs.host, + port=3Dargs.port, + calc_time=3Dargs.calc_time, + sample_pages=3Dargs.sample_page= s)) + print(json.dumps(data)) + elif args.command =3D=3D 'predict': + data =3D json.load(sys.stdin) + model =3D MemoryModel(data) + run_predict_cmd(model) + +if __name__ =3D=3D '__main__': + main() --=20 2.30.2