From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 31D6AC0015E for ; Sun, 6 Aug 2023 06:19:42 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1qSX6U-0001z8-Fr; Sun, 06 Aug 2023 02:19:02 -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 1qSX6S-0001yb-5b for qemu-devel@nongnu.org; Sun, 06 Aug 2023 02:19:00 -0400 Received: from mail-pj1-x102a.google.com ([2607:f8b0:4864:20::102a]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1qSX6N-0001gj-Km for qemu-devel@nongnu.org; Sun, 06 Aug 2023 02:18:59 -0400 Received: by mail-pj1-x102a.google.com with SMTP id 98e67ed59e1d1-26837895fbbso2433362a91.3 for ; Sat, 05 Aug 2023 23:16:52 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=smartx-com.20221208.gappssmtp.com; s=20221208; t=1691302611; x=1691907411; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=HwRp0iQ9VC/T26qCFrCFgI4AHBhG5tzKaJpRzIWjz90=; b=0aCTBv8iAhhBMcIXdnLHWOigTc2y43FBY09/tnAnnNQrXUn/p5Pp3Rwf3hMc8oQ23z E8lJul4YU6Tr5ijJs0czbFflHGwDMqoi50A2gKYg4lsFZx+lFITUHVFcdiAtUWst5Oop +nRQLA8GZijgw2+ErU8A3y3DNzRe7bQ0T4KrfduIn/OCLrZiOUiPuxwqogUhDdGB4Tpo uhaMRFpKuDF684Ay+WtjvzFTDqBueD/ElvZrRs0ZWahWuTJm8Y2eRwqQHywNP3MyW1hQ syIRsOgXQ4ywMcwdiGdW8Od37SrE9u4ruvlptiHraak2O9/UTjRyI9vvrS7Ba80KraML IV9w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1691302611; x=1691907411; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=HwRp0iQ9VC/T26qCFrCFgI4AHBhG5tzKaJpRzIWjz90=; b=aTQiFwr2p04ZnRijjZykkPONUbtitcQ1MWHMBHHJ6+YSPOPLuw/FwtZF3gah461lJr q5INZeyP4jdbWN3YWoT2rPyO1MZbfxVSpQSukQuRIoTcqQ0nG/gCN7sVAJlIyV5g7jLK C79bxyTjJAS/MZy2tSoNEvt3JFooswKNQQzDeWr1383UquLt1/yPr9rB5OxptUERj0sn y58AqquCazsJs1Sd4NydhiXnNL5DQhiKiZ4zLUFFgOh6/VX7+J+PsbSHzZnKt8PKD8EL EfPTKH1fTCP7Zm2sP0oHb2mI61vrMZO6xbVKWpp6WT+Okid2OGUAQwCwkVUFzmQLcATQ Qh/g== X-Gm-Message-State: AOJu0YwBRrfPutEBn7wASmqqpUSeiSOg4A0nM4XKUHya93CvtXPiGt9v mfGLjLUFgCImK+srx+b5/PacKZsN0WOatPDMcqge1g== X-Google-Smtp-Source: AGHT+IGM09emoQmm0WPR839Az/kHvc5loRbZNkMz5CpJOoxMvCerH0r7yqNAMDzTVBruqNA1Vnvn3qfpeP8vixeXoyI= X-Received: by 2002:a17:90b:4f48:b0:268:b64b:f684 with SMTP id pj8-20020a17090b4f4800b00268b64bf684mr5649797pjb.33.1691302611058; Sat, 05 Aug 2023 23:16:51 -0700 (PDT) MIME-Version: 1.0 References: <8ddb0d40d143f77aab8f602bd494e01e5fa01614.1691161009.git.gudkov.andrei@huawei.com> In-Reply-To: <8ddb0d40d143f77aab8f602bd494e01e5fa01614.1691161009.git.gudkov.andrei@huawei.com> From: Yong Huang Date: Sun, 6 Aug 2023 14:16:34 +0800 Message-ID: Subject: Re: [PATCH v2] migration/calc-dirty-rate: millisecond-granularity period To: Andrei Gudkov Cc: qemu-devel@nongnu.org, quintela@redhat.com, peterx@redhat.com, leobras@redhat.com, eblake@redhat.com, armbru@redhat.com Content-Type: multipart/alternative; boundary="0000000000005719b806023b1386" Received-SPF: none client-ip=2607:f8b0:4864:20::102a; envelope-from=yong.huang@smartx.com; helo=mail-pj1-x102a.google.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, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_NONE=0.001 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: , Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org --0000000000005719b806023b1386 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Fri, Aug 4, 2023 at 11:03=E2=80=AFPM Andrei Gudkov wrote: > Introduces alternative argument calc-time-ms, which is the > the same as calc-time but accepts millisecond value. > Millisecond granularity allows to make predictions whether > migration will succeed or not. To do this, calculate dirty > rate with calc-time-ms set to max allowed downtime, convert > measured rate into volume of dirtied memory, and divide by > network throughput. If the value is lower than max allowed > Not for the patch, I'm just curious about how the predication decides the network throughput, I mean QEMU predicts if migration will converge based on how fast it sends the data, not the actual bandwidth of the interface, which one the prediction uses? > downtime, then migration will converge. > > Measurement results for single thread randomly writing to > a 1/4/24GiB memory region: > > +--------------+-----------------------------------------------+ > | calc-time-ms | dirty rate MiB/s | > | +----------------+---------------+--------------+ > | | theoretical | page-sampling | dirty-bitmap | > | | (at 3M wr/sec) | | | > +--------------+----------------+---------------+--------------+ > | 1GiB | > +--------------+----------------+---------------+--------------+ > | 100 | 6996 | 7100 | 3192 | > | 200 | 4606 | 4660 | 2655 | > | 300 | 3305 | 3280 | 2371 | > | 400 | 2534 | 2525 | 2154 | > | 500 | 2041 | 2044 | 1871 | > | 750 | 1365 | 1341 | 1358 | > | 1000 | 1024 | 1052 | 1025 | > | 1500 | 683 | 678 | 684 | > | 2000 | 512 | 507 | 513 | > +--------------+----------------+---------------+--------------+ > | 4GiB | > +--------------+----------------+---------------+--------------+ > | 100 | 10232 | 8880 | 4070 | > | 200 | 8954 | 8049 | 3195 | > | 300 | 7889 | 7193 | 2881 | > | 400 | 6996 | 6530 | 2700 | > | 500 | 6245 | 5772 | 2312 | > | 750 | 4829 | 4586 | 2465 | > | 1000 | 3865 | 3780 | 2178 | > | 1500 | 2694 | 2633 | 2004 | > | 2000 | 2041 | 2031 | 1789 | > +--------------+----------------+---------------+--------------+ > | 24GiB | > +--------------+----------------+---------------+--------------+ > | 100 | 11495 | 8640 | 5597 | > | 200 | 11226 | 8616 | 3527 | > | 300 | 10965 | 8386 | 2355 | > | 400 | 10713 | 8370 | 2179 | > | 500 | 10469 | 8196 | 2098 | > | 750 | 9890 | 7885 | 2556 | > | 1000 | 9354 | 7506 | 2084 | > | 1500 | 8397 | 6944 | 2075 | > | 2000 | 7574 | 6402 | 2062 | > +--------------+----------------+---------------+--------------+ > > Theoretical values are computed according to the following formula: > size * (1 - (1-(4096/size))^(time*wps)) / (time * 2^20), > where size is in bytes, time is in seconds, and wps is number of > writes per second. > > Signed-off-by: Andrei Gudkov > --- > qapi/migration.json | 14 ++++++-- > migration/dirtyrate.h | 12 ++++--- > migration/dirtyrate.c | 81 +++++++++++++++++++++++++------------------ > 3 files changed, 67 insertions(+), 40 deletions(-) > > [...] > diff --git a/qapi/migration.json b/qapi/migration.json > index 8843e74b59..82493d6a57 100644 > --- a/qapi/migration.json > +++ b/qapi/migration.json > @@ -1849,7 +1849,11 @@ > # @start-time: start time in units of second for calculation > # > # @calc-time: time period for which dirty page rate was measured > -# (in seconds) > +# (rounded down to seconds). > Does there need an extra comment to emphasize that calc-time shows zero if the calc-time-ms is lower than 1000? > +# > +# @calc-time-ms: actual time period for which dirty page rate was > +# measured (in milliseconds). Value may be larger than requested > +# time period due to measurement overhead. > # > # @sample-pages: number of sampled pages per GiB of guest memory. > # Valid only in page-sampling mode (Since 6.1) > @@ -1866,6 +1870,7 @@ > 'status': 'DirtyRateStatus', > 'start-time': 'int64', > 'calc-time': 'int64', > + 'calc-time-ms': 'int64', > 'sample-pages': 'uint64', > 'mode': 'DirtyRateMeasureMode', > '*vcpu-dirty-rate': [ 'DirtyRateVcpu' ] } } > @@ -1908,6 +1913,10 @@ > # dirty during @calc-time period, further writes to this page will > # not increase dirty page rate anymore. > # > +# @calc-time-ms: the same as @calc-time but in milliseconds. These > +# two arguments are mutually exclusive. Exactly one of them must > +# be specified. (Since 8.1) > +# > # @sample-pages: number of sampled pages per each GiB of guest memory. > # Default value is 512. For 4KiB guest pages this corresponds to > # sampling ratio of 0.2%. This argument is used only in page > @@ -1925,7 +1934,8 @@ > # 'sample-pages': 512} } > # <- { "return": {} } > ## > -{ 'command': 'calc-dirty-rate', 'data': {'calc-time': 'int64', > +{ 'command': 'calc-dirty-rate', 'data': {'*calc-time': 'int64', > + '*calc-time-ms': 'int64', > '*sample-pages': 'int', > '*mode': 'DirtyRateMeasureMode'= } > } > > diff --git a/migration/dirtyrate.h b/migration/dirtyrate.h > index 594a5c0bb6..869c060941 100644 > --- a/migration/dirtyrate.h > +++ b/migration/dirtyrate.h > @@ -31,10 +31,12 @@ > #define MIN_RAMBLOCK_SIZE 128 > > /* > - * Take 1s as minimum time for calculation duration > + * Allowed range for dirty page rate calculation (in milliseconds). > + * Lower limit relates to the smallest realistic downtime it > + * makes sense to impose on migration. > */ > -#define MIN_FETCH_DIRTYRATE_TIME_SEC 1 > -#define MAX_FETCH_DIRTYRATE_TIME_SEC 60 > +#define MIN_CALC_TIME_MS 50 > +#define MAX_CALC_TIME_MS 60000 > > /* > * Take 1/16 pages in 1G as the maxmum sample page count > @@ -44,7 +46,7 @@ > > struct DirtyRateConfig { > uint64_t sample_pages_per_gigabytes; /* sample pages per GB */ > - int64_t sample_period_seconds; /* time duration between two sampling > */ > + int64_t calc_time_ms; /* desired calculation time (in milliseconds) = */ > DirtyRateMeasureMode mode; /* mode of dirtyrate measurement */ > }; > > @@ -73,7 +75,7 @@ typedef struct SampleVMStat { > struct DirtyRateStat { > int64_t dirty_rate; /* dirty rate in MB/s */ > int64_t start_time; /* calculation start time in units of second */ > - int64_t calc_time; /* time duration of two sampling in units of > second */ > + int64_t calc_time_ms; /* actual calculation time (in milliseconds) *= / > uint64_t sample_pages; /* sample pages per GB */ > union { > SampleVMStat page_sampling; diff --git a/migration/dirtyrate.c b/migration/dirtyrate.c > index 84f1b0fb20..90fb336329 100644 > --- a/migration/dirtyrate.c > +++ b/migration/dirtyrate.c > @@ -57,6 +57,8 @@ static int64_t dirty_stat_wait(int64_t msec, int64_t > initial_time) > msec =3D current_time - initial_time; > } else { > g_usleep((msec + initial_time - current_time) * 1000); > + /* g_usleep may overshoot */ > + msec =3D qemu_clock_get_ms(QEMU_CLOCK_REALTIME) - initial_time; > The optimization could be a standalone commit along with the content below(see the following comment)? > } > > return msec; > @@ -77,9 +79,12 @@ static int64_t do_calculate_dirtyrate(DirtyPageRecord > dirty_pages, > { > uint64_t increased_dirty_pages =3D > dirty_pages.end_pages - dirty_pages.start_pages; > - uint64_t memory_size_MiB =3D > qemu_target_pages_to_MiB(increased_dirty_pages); > - > - return memory_size_MiB * 1000 / calc_time_ms; > + /* > + * multiply by 1000ms/s _before_ converting down to megabytes > + * to avoid losing precision > + */ > + return qemu_target_pages_to_MiB(increased_dirty_pages * 1000) / > + calc_time_ms; > Code optimization, could be in a standalone commit. > } > > void global_dirty_log_change(unsigned int flag, bool start) > @@ -183,10 +188,9 @@ retry: > return duration; > } > > -static bool is_sample_period_valid(int64_t sec) > +static bool is_calc_time_valid(int64_t msec) > { > - if (sec < MIN_FETCH_DIRTYRATE_TIME_SEC || > - sec > MAX_FETCH_DIRTYRATE_TIME_SEC) { > + if ((msec < MIN_CALC_TIME_MS) || (msec > MAX_CALC_TIME_MS)) { > return false; > } > > @@ -219,7 +223,8 @@ static struct DirtyRateInfo > *query_dirty_rate_info(void) > > info->status =3D CalculatingState; > info->start_time =3D DirtyStat.start_time; > - info->calc_time =3D DirtyStat.calc_time; > + info->calc_time_ms =3D DirtyStat.calc_time_ms; > + info->calc_time =3D DirtyStat.calc_time_ms / 1000; > info->sample_pages =3D DirtyStat.sample_pages; > info->mode =3D dirtyrate_mode; > > @@ -258,7 +263,7 @@ static void init_dirtyrate_stat(int64_t start_time, > { > DirtyStat.dirty_rate =3D -1; > DirtyStat.start_time =3D start_time; > - DirtyStat.calc_time =3D config.sample_period_seconds; > + DirtyStat.calc_time_ms =3D config.calc_time_ms; > DirtyStat.sample_pages =3D config.sample_pages_per_gigabytes; > > switch (config.mode) { > @@ -568,7 +573,6 @@ static inline void dirtyrate_manual_reset_protect(voi= d) > > static void calculate_dirtyrate_dirty_bitmap(struct DirtyRateConfig > config) > { > - int64_t msec =3D 0; > int64_t start_time; > DirtyPageRecord dirty_pages; > > @@ -596,9 +600,7 @@ static void calculate_dirtyrate_dirty_bitmap(struct > DirtyRateConfig config) > start_time =3D qemu_clock_get_ms(QEMU_CLOCK_REALTIME); > DirtyStat.start_time =3D start_time / 1000; > > - msec =3D config.sample_period_seconds * 1000; > - msec =3D dirty_stat_wait(msec, start_time); > - DirtyStat.calc_time =3D msec / 1000; > + DirtyStat.calc_time_ms =3D dirty_stat_wait(config.calc_time_ms, > start_time); > > /* > * do two things. > @@ -609,12 +611,12 @@ static void calculate_dirtyrate_dirty_bitmap(struct > DirtyRateConfig config) > > record_dirtypages_bitmap(&dirty_pages, false); > > - DirtyStat.dirty_rate =3D do_calculate_dirtyrate(dirty_pages, msec); > + DirtyStat.dirty_rate =3D do_calculate_dirtyrate(dirty_pages, > + DirtyStat.calc_time_ms= ); > } > > static void calculate_dirtyrate_dirty_ring(struct DirtyRateConfig config= ) > { > - int64_t duration; > uint64_t dirtyrate =3D 0; > uint64_t dirtyrate_sum =3D 0; > int i =3D 0; > @@ -625,12 +627,10 @@ static void calculate_dirtyrate_dirty_ring(struct > DirtyRateConfig config) > DirtyStat.start_time =3D qemu_clock_get_ms(QEMU_CLOCK_REALTIME) / 10= 00; > > /* calculate vcpu dirtyrate */ > - duration =3D vcpu_calculate_dirtyrate(config.sample_period_seconds * > 1000, > - &DirtyStat.dirty_ring, > - GLOBAL_DIRTY_DIRTY_RATE, > - true); > - > - DirtyStat.calc_time =3D duration / 1000; > + DirtyStat.calc_time_ms =3D vcpu_calculate_dirtyrate(config.calc_time= _ms, > + > &DirtyStat.dirty_ring, > + > GLOBAL_DIRTY_DIRTY_RATE, > + true); > > /* calculate vm dirtyrate */ > for (i =3D 0; i < DirtyStat.dirty_ring.nvcpu; i++) { > @@ -646,7 +646,6 @@ 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; > > rcu_read_lock(); > @@ -656,17 +655,16 @@ static void calculate_dirtyrate_sample_vm(struct > DirtyRateConfig config) > } > rcu_read_unlock(); > > - msec =3D config.sample_period_seconds * 1000; > - msec =3D dirty_stat_wait(msec, initial_time); > + DirtyStat.calc_time_ms =3D dirty_stat_wait(config.calc_time_ms, > + initial_time); > DirtyStat.start_time =3D initial_time / 1000; > - DirtyStat.calc_time =3D msec / 1000; > > rcu_read_lock(); > if (!compare_page_hash_info(block_dinfo, block_count)) { > goto out; > } > > - update_dirtyrate(msec); > + update_dirtyrate(DirtyStat.calc_time_ms); > > out: > rcu_read_unlock(); > @@ -711,7 +709,10 @@ void *get_dirtyrate_thread(void *arg) > return NULL; > } > > -void qmp_calc_dirty_rate(int64_t calc_time, > +void qmp_calc_dirty_rate(bool has_calc_time, > + int64_t calc_time, > + bool has_calc_time_ms, > + int64_t calc_time_ms, > bool has_sample_pages, > int64_t sample_pages, > bool has_mode, > @@ -731,10 +732,21 @@ void qmp_calc_dirty_rate(int64_t calc_time, > return; > } > > - if (!is_sample_period_valid(calc_time)) { > - error_setg(errp, "calc-time is out of range[%d, %d].", > - MIN_FETCH_DIRTYRATE_TIME_SEC, > - MAX_FETCH_DIRTYRATE_TIME_SEC); > + if ((int)has_calc_time + (int)has_calc_time_ms !=3D 1) { > + error_setg(errp, "Exactly one of calc-time and calc-time-ms must= " > + " be specified"); > + return; > + } > + if (has_calc_time) { > + /* > + * The worst thing that can happen due to overflow is that > + * invalid value will become valid. > + */ > + calc_time_ms =3D calc_time * 1000; > + } > + if (!is_calc_time_valid(calc_time_ms)) { > + error_setg(errp, "Calculation time is out of range[%dms, %dms]."= , > + MIN_CALC_TIME_MS, MAX_CALC_TIME_MS); > return; > } > > @@ -781,7 +793,7 @@ void qmp_calc_dirty_rate(int64_t calc_time, > return; > } > > - config.sample_period_seconds =3D calc_time; > + config.calc_time_ms =3D calc_time_ms; > config.sample_pages_per_gigabytes =3D sample_pages; > config.mode =3D mode; > > @@ -867,8 +879,11 @@ void hmp_calc_dirty_rate(Monitor *mon, const QDict > *qdict) > mode =3D DIRTY_RATE_MEASURE_MODE_DIRTY_RING; > } > > - qmp_calc_dirty_rate(sec, has_sample_pages, sample_pages, true, > - mode, &err); > + qmp_calc_dirty_rate(true, sec, /* calc_time */ > + false, 0, /* calc_time_ms */ > + has_sample_pages, sample_pages, > + true, mode, > + &err); > if (err) { > hmp_handle_error(mon, err); > return; > -- > 2.30.2 > > The patch set works for me, and I'm inclined to split it into two commits as I point out above. Thanks Yong --=20 Best regards --0000000000005719b806023b1386 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


On Fri, Aug 4, 202= 3 at 11:03=E2=80=AFPM Andrei Gudkov <gudkov.andrei@huawei.com> wrote:
= Introduces alternative argument calc-time-ms, which is the
the same as calc-time but accepts millisecond value.
Millisecond granularity allows to make predictions whether
migration will succeed or not. To do this, calculate dirty
rate with calc-time-ms set to max allowed downtime, convert
measured rate into volume of dirtied memory, and divide by
network throughput. If the value is lower than max allowed
=
Not for the patch, I'm just curious about how the predicat= ion
decides the network throughput, I mean QEMU predicts<= /div>
if migration will converge based on how fast it sends the= data,
not the actual bandwidth of the interface, which o= ne the
prediction uses?
downtime, then migration will converge.

Measurement results for single thread randomly writing to
a 1/4/24GiB memory region:

+--------------+-----------------------------------------------+
| calc-time-ms |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 dir= ty rate MiB/s=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0|
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 +----------------+-------= --------+--------------+
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 | theoretical=C2=A0 =C2= =A0 | page-sampling | dirty-bitmap |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 | (at 3M wr/sec) |=C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0|=C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 |
+--------------+----------------+---------------+--------------+
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A01GiB=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0|
+--------------+----------------+---------------+--------------+
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 100 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A06996 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 7100 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A03192 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 200 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A04606 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 4660 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A02655 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 300 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A03305 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 3280 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A02371 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 400 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A02534 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 2525 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A02154 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 500 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A02041 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 2044 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A01871 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 750 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A01365 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 1341 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A01358 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A01000 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A01024 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 1052 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A01025 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A01500 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 683 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0678 |=C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 684 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A02000 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 512 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0507 |=C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 513 |
+--------------+----------------+---------------+--------------+
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A04GiB=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0|
+--------------+----------------+---------------+--------------+
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 100 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= 10232 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 8880 |=C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A04070 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 200 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A08954 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 8049 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A03195 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 300 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A07889 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 7193 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A02881 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 400 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A06996 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 6530 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A02700 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 500 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A06245 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 5772 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A02312 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 750 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A04829 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 4586 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A02465 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A01000 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A03865 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 3780 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A02178 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A01500 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A02694 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 2633 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A02004 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A02000 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A02041 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 2031 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A01789 |
+--------------+----------------+---------------+--------------+
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A024GiB=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 |
+--------------+----------------+---------------+--------------+
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 100 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= 11495 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 8640 |=C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A05597 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 200 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= 11226 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 8616 |=C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A03527 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 300 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= 10965 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 8386 |=C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A02355 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 400 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= 10713 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 8370 |=C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A02179 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 500 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= 10469 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 8196 |=C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A02098 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 750 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A09890 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 7885 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A02556 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A01000 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A09354 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 7506 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A02084 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A01500 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A08397 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 6944 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A02075 |
|=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A02000 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A07574 |=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 6402 |=C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A02062 |
+--------------+----------------+---------------+--------------+

Theoretical values are computed according to the following formula:
size * (1 - (1-(4096/size))^(time*wps)) / (time * 2^20),
where size is in bytes, time is in seconds, and wps is number of
writes per second.

Signed-off-by: Andrei Gudkov <gudkov.andrei@huawei.com>
---
=C2=A0qapi/migration.json=C2=A0 =C2=A0| 14 ++++++--
=C2=A0migration/dirtyrate.h | 12 ++++---
=C2=A0migration/dirtyrate.c | 81 +++++++++++++++++++++++++-----------------= -
=C2=A03 files changed, 67 insertions(+), 40 deletions(-)

[...]=C2=A0
diff --git a/qapi/migration.json b/qapi/migration.json
index 8843e74b59..82493d6a57 100644
--- a/qapi/migration.json
+++ b/qapi/migration.json
@@ -1849,7 +1849,11 @@
=C2=A0# @start-time: start time in units of second for calculation
=C2=A0#
=C2=A0# @calc-time: time period for which dirty page rate was measured
-#=C2=A0 =C2=A0 =C2=A0(in seconds)
+#=C2=A0 =C2=A0 =C2=A0(rounded down to seconds).
Does there need an extra comment to emphasize that calc-time shows
=
zero if the calc-time-ms is lower than 1000?
+#
+# @calc-time-ms: actual time period for which dirty page rate was
+#=C2=A0 =C2=A0 =C2=A0measured (in milliseconds).=C2=A0 Value may be larger= than requested
+#=C2=A0 =C2=A0 =C2=A0time period due to measurement overhead.
=C2=A0#
=C2=A0# @sample-pages: number of sampled pages per GiB of guest memory.
=C2=A0#=C2=A0 =C2=A0 =C2=A0Valid only in page-sampling mode (Since 6.1)
@@ -1866,6 +1870,7 @@
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 'status': 'DirtyRateS= tatus',
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 'start-time': 'int64&= #39;,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 'calc-time': 'int64&#= 39;,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'calc-time-ms': 'int6= 4',
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 'sample-pages': 'uint= 64',
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 'mode': 'DirtyRateMea= sureMode',
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 '*vcpu-dirty-rate': [ = 9;DirtyRateVcpu' ] } }
@@ -1908,6 +1913,10 @@
=C2=A0#=C2=A0 =C2=A0 =C2=A0dirty during @calc-time period, further writes t= o this page will
=C2=A0#=C2=A0 =C2=A0 =C2=A0not increase dirty page rate anymore.
=C2=A0#
+# @calc-time-ms: the same as @calc-time but in milliseconds.=C2=A0 These +#=C2=A0 =C2=A0 two arguments are mutually exclusive.=C2=A0 Exactly one of = them must
+#=C2=A0 =C2=A0 be specified. (Since 8.1)
+#
=C2=A0# @sample-pages: number of sampled pages per each GiB of guest memory= .
=C2=A0#=C2=A0 =C2=A0 =C2=A0Default value is 512.=C2=A0 For 4KiB guest pages= this corresponds to
=C2=A0#=C2=A0 =C2=A0 =C2=A0sampling ratio of 0.2%.=C2=A0 This argument is u= sed only in page
@@ -1925,7 +1934,8 @@
=C2=A0#=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'sample-pages': 512} }
=C2=A0# <- { "return": {} }
=C2=A0##
-{ 'command': 'calc-dirty-rate', 'data': {'calc= -time': 'int64',
+{ 'command': 'calc-dirty-rate', 'data': {'*cal= c-time': 'int64',
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0&#= 39;*calc-time-ms': 'int64',
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 &= #39;*sample-pages': 'int',
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 &= #39;*mode': 'DirtyRateMeasureMode'} }

diff --git a/migration/dirtyrate.h b/migration/dirtyrate.h
index 594a5c0bb6..869c060941 100644
--- a/migration/dirtyrate.h
+++ b/migration/dirtyrate.h
@@ -31,10 +31,12 @@
=C2=A0#define MIN_RAMBLOCK_SIZE=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0128

=C2=A0/*
- * Take 1s as minimum time for calculation duration
+ * Allowed range for dirty page rate calculation (in milliseconds).
+ * Lower limit relates to the smallest realistic downtime it
+ * makes sense to impose on migration.
=C2=A0 */
-#define MIN_FETCH_DIRTYRATE_TIME_SEC=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 1
-#define MAX_FETCH_DIRTYRATE_TIME_SEC=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 60
+#define MIN_CALC_TIME_MS=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 50
+#define MAX_CALC_TIME_MS=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A060000

=C2=A0/*
=C2=A0 * Take 1/16 pages in 1G as the maxmum sample page count
@@ -44,7 +46,7 @@

=C2=A0struct DirtyRateConfig {
=C2=A0 =C2=A0 =C2=A0uint64_t sample_pages_per_gigabytes; /* sample pages pe= r GB */
-=C2=A0 =C2=A0 int64_t sample_period_seconds; /* time duration between two = sampling */
+=C2=A0 =C2=A0 int64_t calc_time_ms; /* desired calculation time (in millis= econds) */
=C2=A0 =C2=A0 =C2=A0DirtyRateMeasureMode mode; /* mode of dirtyrate measure= ment */
=C2=A0};

@@ -73,7 +75,7 @@ typedef struct SampleVMStat {
=C2=A0struct DirtyRateStat {
=C2=A0 =C2=A0 =C2=A0int64_t dirty_rate; /* dirty rate in MB/s */
=C2=A0 =C2=A0 =C2=A0int64_t start_time; /* calculation start time in units = of second */
-=C2=A0 =C2=A0 int64_t calc_time; /* time duration of two sampling in units= of second */
+=C2=A0 =C2=A0 int64_t calc_time_ms; /* actual calculation time (in millise= conds) */
=C2=A0 =C2=A0 =C2=A0uint64_t sample_pages; /* sample pages per GB */
=C2=A0 =C2=A0 =C2=A0union {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0SampleVMStat page_sampling;
<= blockquote class=3D"gmail_quote" style=3D"margin:0px 0px 0px 0.8ex;border-l= eft-width:1px;border-left-style:solid;border-left-color:rgb(204,204,204);pa= dding-left:1ex"> diff --git a/migration/dirtyrate.c b/migration/dirtyrate.c
index 84f1b0fb20..90fb336329 100644
--- a/migration/dirtyrate.c
+++ b/migration/dirtyrate.c
@@ -57,6 +57,8 @@ static int64_t dirty_stat_wait(int64_t msec, int64_t init= ial_time)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0msec =3D current_time - initial_time;
=C2=A0 =C2=A0 =C2=A0} else {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0g_usleep((msec + initial_time - current_t= ime) * 1000);
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* g_usleep may overshoot */
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 msec =3D qemu_clock_get_ms(QEMU_CLOCK_REALTIME= ) - initial_time;
The optimization could be a = standalone commit along with the content below(see the following comment)?<= /div>
=C2=A0 =C2=A0 =C2=A0}

=C2=A0 =C2=A0 =C2=A0return msec;
@@ -77,9 +79,12 @@ static int64_t do_calculate_dirtyrate(DirtyPageRecord di= rty_pages,
=C2=A0{
=C2=A0 =C2=A0 =C2=A0uint64_t increased_dirty_pages =3D
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0dirty_pages.end_pages - dirty_pages.start= _pages;
-=C2=A0 =C2=A0 uint64_t memory_size_MiB =3D qemu_target_pages_to_MiB(increa= sed_dirty_pages);
-
-=C2=A0 =C2=A0 return memory_size_MiB * 1000 / calc_time_ms;
+=C2=A0 =C2=A0 /*
+=C2=A0 =C2=A0 =C2=A0* multiply by 1000ms/s _before_ converting down to meg= abytes
+=C2=A0 =C2=A0 =C2=A0* to avoid losing precision
+=C2=A0 =C2=A0 =C2=A0*/
+=C2=A0 =C2=A0 return qemu_target_pages_to_MiB(increased_dirty_pages * 1000= ) /
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 calc_time_ms;
Cod= e optimization, could be in a standalone commit.
=C2=A0}

=C2=A0void global_dirty_log_change(unsigned int flag, bool start)
@@ -183,10 +188,9 @@ retry:
=C2=A0 =C2=A0 =C2=A0return duration;
=C2=A0}

-static bool is_sample_period_valid(int64_t sec)
+static bool is_calc_time_valid(int64_t msec)
=C2=A0{
-=C2=A0 =C2=A0 if (sec < MIN_FETCH_DIRTYRATE_TIME_SEC ||
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 sec > MAX_FETCH_DIRTYRATE_TIME_SEC) {
+=C2=A0 =C2=A0 if ((msec < MIN_CALC_TIME_MS) || (msec > MAX_CALC_TIME= _MS)) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return false;
=C2=A0 =C2=A0 =C2=A0}

@@ -219,7 +223,8 @@ static struct DirtyRateInfo *query_dirty_rate_info(void= )

=C2=A0 =C2=A0 =C2=A0info->status =3D CalculatingState;
=C2=A0 =C2=A0 =C2=A0info->start_time =3D DirtyStat.start_time;
-=C2=A0 =C2=A0 info->calc_time =3D DirtyStat.calc_time;
+=C2=A0 =C2=A0 info->calc_time_ms =3D DirtyStat.calc_time_ms;
+=C2=A0 =C2=A0 info->calc_time =3D DirtyStat.calc_time_ms / 1000;
=C2=A0 =C2=A0 =C2=A0info->sample_pages =3D DirtyStat.sample_pages;
=C2=A0 =C2=A0 =C2=A0info->mode =3D dirtyrate_mode;

@@ -258,7 +263,7 @@ static void init_dirtyrate_stat(int64_t start_time,
=C2=A0{
=C2=A0 =C2=A0 =C2=A0DirtyStat.dirty_rate =3D -1;
=C2=A0 =C2=A0 =C2=A0DirtyStat.start_time =3D start_time;
-=C2=A0 =C2=A0 DirtyStat.calc_time =3D config.sample_period_seconds;
+=C2=A0 =C2=A0 DirtyStat.calc_time_ms =3D config.calc_time_ms;
=C2=A0 =C2=A0 =C2=A0DirtyStat.sample_pages =3D config.sample_pages_per_giga= bytes;

=C2=A0 =C2=A0 =C2=A0switch (config.mode) {
@@ -568,7 +573,6 @@ static inline void dirtyrate_manual_reset_protect(void)=

=C2=A0static void calculate_dirtyrate_dirty_bitmap(struct DirtyRateConfig c= onfig)
=C2=A0{
-=C2=A0 =C2=A0 int64_t msec =3D 0;
=C2=A0 =C2=A0 =C2=A0int64_t start_time;
=C2=A0 =C2=A0 =C2=A0DirtyPageRecord dirty_pages;

@@ -596,9 +600,7 @@ static void calculate_dirtyrate_dirty_bitmap(struct Dir= tyRateConfig config)
=C2=A0 =C2=A0 =C2=A0start_time =3D qemu_clock_get_ms(QEMU_CLOCK_REALTIME);<= br> =C2=A0 =C2=A0 =C2=A0DirtyStat.start_time =3D start_time / 1000;

-=C2=A0 =C2=A0 msec =3D config.sample_period_seconds * 1000;
-=C2=A0 =C2=A0 msec =3D dirty_stat_wait(msec, start_time);
-=C2=A0 =C2=A0 DirtyStat.calc_time =3D msec / 1000;
+=C2=A0 =C2=A0 DirtyStat.calc_time_ms =3D dirty_stat_wait(config.calc_time_= ms, start_time);

=C2=A0 =C2=A0 =C2=A0/*
=C2=A0 =C2=A0 =C2=A0 * do two things.
@@ -609,12 +611,12 @@ static void calculate_dirtyrate_dirty_bitmap(struct D= irtyRateConfig config)

=C2=A0 =C2=A0 =C2=A0record_dirtypages_bitmap(&dirty_pages, false);

-=C2=A0 =C2=A0 DirtyStat.dirty_rate =3D do_calculate_dirtyrate(dirty_pages,= msec);
+=C2=A0 =C2=A0 DirtyStat.dirty_rate =3D do_calculate_dirtyrate(dirty_pages,=
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 DirtyStat.calc_time_ms);
=C2=A0}

=C2=A0static void calculate_dirtyrate_dirty_ring(struct DirtyRateConfig con= fig)
=C2=A0{
-=C2=A0 =C2=A0 int64_t duration;
=C2=A0 =C2=A0 =C2=A0uint64_t dirtyrate =3D 0;
=C2=A0 =C2=A0 =C2=A0uint64_t dirtyrate_sum =3D 0;
=C2=A0 =C2=A0 =C2=A0int i =3D 0;
@@ -625,12 +627,10 @@ static void calculate_dirtyrate_dirty_ring(struct Dir= tyRateConfig config)
=C2=A0 =C2=A0 =C2=A0DirtyStat.start_time =3D qemu_clock_get_ms(QEMU_CLOCK_R= EALTIME) / 1000;

=C2=A0 =C2=A0 =C2=A0/* calculate vcpu dirtyrate */
-=C2=A0 =C2=A0 duration =3D vcpu_calculate_dirtyrate(config.sample_period_s= econds * 1000,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 &Dir= tyStat.dirty_ring,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 GLOBAL_D= IRTY_DIRTY_RATE,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 true); -
-=C2=A0 =C2=A0 DirtyStat.calc_time =3D duration / 1000;
+=C2=A0 =C2=A0 DirtyStat.calc_time_ms =3D vcpu_calculate_dirtyrate(config.c= alc_time_ms,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 &DirtyStat.dirty_ring,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 GLOBAL_DIRTY_DIRTY_RATE,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 true);

=C2=A0 =C2=A0 =C2=A0/* calculate vm dirtyrate */
=C2=A0 =C2=A0 =C2=A0for (i =3D 0; i < DirtyStat.dirty_ring.nvcpu; i++) {=
@@ -646,7 +646,6 @@ static void calculate_dirtyrate_sample_vm(struct DirtyR= ateConfig config)
=C2=A0{
=C2=A0 =C2=A0 =C2=A0struct RamblockDirtyInfo *block_dinfo =3D NULL;
=C2=A0 =C2=A0 =C2=A0int block_count =3D 0;
-=C2=A0 =C2=A0 int64_t msec =3D 0;
=C2=A0 =C2=A0 =C2=A0int64_t initial_time;

=C2=A0 =C2=A0 =C2=A0rcu_read_lock();
@@ -656,17 +655,16 @@ static void calculate_dirtyrate_sample_vm(struct Dirt= yRateConfig config)
=C2=A0 =C2=A0 =C2=A0}
=C2=A0 =C2=A0 =C2=A0rcu_read_unlock();

-=C2=A0 =C2=A0 msec =3D config.sample_period_seconds * 1000;
-=C2=A0 =C2=A0 msec =3D dirty_stat_wait(msec, initial_time);
+=C2=A0 =C2=A0 DirtyStat.calc_time_ms =3D dirty_stat_wait(config.calc_time_= ms,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0initial_time);
=C2=A0 =C2=A0 =C2=A0DirtyStat.start_time =3D initial_time / 1000;
-=C2=A0 =C2=A0 DirtyStat.calc_time =3D msec / 1000;

=C2=A0 =C2=A0 =C2=A0rcu_read_lock();
=C2=A0 =C2=A0 =C2=A0if (!compare_page_hash_info(block_dinfo, block_count)) = {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0goto out;
=C2=A0 =C2=A0 =C2=A0}

-=C2=A0 =C2=A0 update_dirtyrate(msec);
+=C2=A0 =C2=A0 update_dirtyrate(DirtyStat.calc_time_ms);

=C2=A0out:
=C2=A0 =C2=A0 =C2=A0rcu_read_unlock();
@@ -711,7 +709,10 @@ void *get_dirtyrate_thread(void *arg)
=C2=A0 =C2=A0 =C2=A0return NULL;
=C2=A0}

-void qmp_calc_dirty_rate(int64_t calc_time,
+void qmp_calc_dirty_rate(bool has_calc_time,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0int64_t calc_time,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0bool has_calc_time_ms,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0int64_t calc_time_ms,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 bool has_sample_pages,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 int64_t sample_pages,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 bool has_mode,
@@ -731,10 +732,21 @@ void qmp_calc_dirty_rate(int64_t calc_time,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return;
=C2=A0 =C2=A0 =C2=A0}

-=C2=A0 =C2=A0 if (!is_sample_period_valid(calc_time)) {
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 error_setg(errp, "calc-time is out of ran= ge[%d, %d].",
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0MIN_FETCH_DIRTYRATE_TIME_SEC,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0MAX_FETCH_DIRTYRATE_TIME_SEC);
+=C2=A0 =C2=A0 if ((int)has_calc_time + (int)has_calc_time_ms !=3D 1) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 error_setg(errp, "Exactly one of calc-tim= e and calc-time-ms must"
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0" be specified");
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 return;
+=C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 if (has_calc_time) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 /*
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* The worst thing that can happen due to= overflow is that
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0* invalid value will become valid.
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0*/
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 calc_time_ms =3D calc_time * 1000;
+=C2=A0 =C2=A0 }
+=C2=A0 =C2=A0 if (!is_calc_time_valid(calc_time_ms)) {
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 error_setg(errp, "Calculation time is out= of range[%dms, %dms].",
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0MIN_CALC_TIME_MS, MAX_CALC_TIME_MS);
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return;
=C2=A0 =C2=A0 =C2=A0}

@@ -781,7 +793,7 @@ void qmp_calc_dirty_rate(int64_t calc_time,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return;
=C2=A0 =C2=A0 =C2=A0}

-=C2=A0 =C2=A0 config.sample_period_seconds =3D calc_time;
+=C2=A0 =C2=A0 config.calc_time_ms =3D calc_time_ms;
=C2=A0 =C2=A0 =C2=A0config.sample_pages_per_gigabytes =3D sample_pages;
=C2=A0 =C2=A0 =C2=A0config.mode =3D mode;

@@ -867,8 +879,11 @@ void hmp_calc_dirty_rate(Monitor *mon, const QDict *qd= ict)
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0mode =3D DIRTY_RATE_MEASURE_MODE_DIRTY_RI= NG;
=C2=A0 =C2=A0 =C2=A0}

-=C2=A0 =C2=A0 qmp_calc_dirty_rate(sec, has_sample_pages, sample_pages, tru= e,
-=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 mode, &err);
+=C2=A0 =C2=A0 qmp_calc_dirty_rate(true, sec, /* calc_time */
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 false, 0, /* calc_time_ms */
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 has_sample_pages, sample_pages,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 true, mode,
+=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 &err);
=C2=A0 =C2=A0 =C2=A0if (err) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0hmp_handle_error(mon, err);
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0return;
--
2.30.2

The patch set works for me, and I'm inc= lined to split it into two commits as I point out above.=C2=A0

Thanks

Yong

--
Best regards
--0000000000005719b806023b1386--