Dirtylimit and dirtyrate 20231010 patches PULL request

Dirty page rate measurement optimization.
 Please apply, thanks, Yong.
 -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCAA0FiEEaF0CINwmSCgVLlfC3/Ij1rP+y5wFAmUklg0WHHlvbmcuaHVh
 bmdAc21hcnR4LmNvbQAKCRDf8iPWs/7LnIw6D/sECFML6dIDSckhKe1kRBT2oXRd
 lYz4/RRdxPJIJP0zS0yLYXd2d5vHzXdC3hETv7//QiWB2OP/UQnsZ70JCgF4DxIq
 bGL4BUHgaQmyUsyIQXceFznJJQOLs5DczDFJBR2zlQbu3YOAGeNJJmfxmVIEPfcL
 w5NK++g3nVZ3pLJBNblDBUwIW5uoOj6z85rCTc6pvddoTBcAqS0er1aTkuyx9jbB
 VsDdFNJumF6+VnE6QUITHX2knr3UmXc5dfXCJi4CLKRfx3nyK8vYydNawhPtobGD
 0G5MZAPLO3JxdM67EccKj3I/kcAXU4iHu7rV5AscSI/rlfneGjfCup2Xd0we1GCR
 TD07AVDRuW+cS76nEtvDSRj6+8KarZEa3lVbvoPaXIazoHg3GjKylIMTAcGjFlzL
 AnGornOEZSfwNxT3BRvNHFdUNdA9ICZ90sEpWjeu80UNOT2JASOB6JE2VJBFnW81
 4gaoIT74hpI8H2k/x3R8REPnw+SLMI+7VpcA2XcXuOQOdfk0+8zlvxPsJRBaKBzS
 d2es+CpUcmBxZdEQNOi905qxfCFLOhwcstJXyCvFQBp4f8l2SJfIE4liI29qpuma
 hubbOEo/EAVe8ywToHSYj2RU5hnj6gu0n3hvSeye76hS/K+bfvI+HZ3AX+rcRmP8
 vX6Vqs4wdNl5khEnNg==
 =ixu5
 -----END PGP SIGNATURE-----

Merge tag 'dirtylimit-dirtyrate-pull-request-20231010' of https://github.com/newfriday/qemu into staging

Dirtylimit and dirtyrate 20231010 patches PULL request

Dirty page rate measurement optimization.
Please apply, thanks, Yong.

# -----BEGIN PGP SIGNATURE-----
#
# iQJKBAABCAA0FiEEaF0CINwmSCgVLlfC3/Ij1rP+y5wFAmUklg0WHHlvbmcuaHVh
# bmdAc21hcnR4LmNvbQAKCRDf8iPWs/7LnIw6D/sECFML6dIDSckhKe1kRBT2oXRd
# lYz4/RRdxPJIJP0zS0yLYXd2d5vHzXdC3hETv7//QiWB2OP/UQnsZ70JCgF4DxIq
# bGL4BUHgaQmyUsyIQXceFznJJQOLs5DczDFJBR2zlQbu3YOAGeNJJmfxmVIEPfcL
# w5NK++g3nVZ3pLJBNblDBUwIW5uoOj6z85rCTc6pvddoTBcAqS0er1aTkuyx9jbB
# VsDdFNJumF6+VnE6QUITHX2knr3UmXc5dfXCJi4CLKRfx3nyK8vYydNawhPtobGD
# 0G5MZAPLO3JxdM67EccKj3I/kcAXU4iHu7rV5AscSI/rlfneGjfCup2Xd0we1GCR
# TD07AVDRuW+cS76nEtvDSRj6+8KarZEa3lVbvoPaXIazoHg3GjKylIMTAcGjFlzL
# AnGornOEZSfwNxT3BRvNHFdUNdA9ICZ90sEpWjeu80UNOT2JASOB6JE2VJBFnW81
# 4gaoIT74hpI8H2k/x3R8REPnw+SLMI+7VpcA2XcXuOQOdfk0+8zlvxPsJRBaKBzS
# d2es+CpUcmBxZdEQNOi905qxfCFLOhwcstJXyCvFQBp4f8l2SJfIE4liI29qpuma
# hubbOEo/EAVe8ywToHSYj2RU5hnj6gu0n3hvSeye76hS/K+bfvI+HZ3AX+rcRmP8
# vX6Vqs4wdNl5khEnNg==
# =ixu5
# -----END PGP SIGNATURE-----
# gpg: Signature made Mon 09 Oct 2023 20:08:45 EDT
# gpg:                using RSA key 685D0220DC264828152E57C2DFF223D6B3FECB9C
# gpg:                issuer "yong.huang@smartx.com"
# gpg: Good signature from "Yong Huang <yong.huang@smartx.com>" [unknown]
# gpg: WARNING: The key's User ID is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: 685D 0220 DC26 4828 152E  57C2 DFF2 23D6 B3FE CB9C

* tag 'dirtylimit-dirtyrate-pull-request-20231010' of https://github.com/newfriday/qemu:
  migration/dirtyrate: use QEMU_CLOCK_HOST to report start-time
  migration/calc-dirty-rate: millisecond-granularity period

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
This commit is contained in:
Stefan Hajnoczi 2023-10-10 10:21:43 -04:00
commit 9a8981e68c
3 changed files with 134 additions and 58 deletions

View File

@ -189,10 +189,9 @@ retry:
return duration; 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 || if ((msec < MIN_CALC_TIME_MS) || (msec > MAX_CALC_TIME_MS)) {
sec > MAX_FETCH_DIRTYRATE_TIME_SEC) {
return false; return false;
} }
@ -216,7 +215,39 @@ static int dirtyrate_set_state(int *state, int old_state, int new_state)
} }
} }
static struct DirtyRateInfo *query_dirty_rate_info(void) /* Decimal power of given time unit relative to one second */
static int time_unit_to_power(TimeUnit time_unit)
{
switch (time_unit) {
case TIME_UNIT_SECOND:
return 0;
case TIME_UNIT_MILLISECOND:
return -3;
default:
assert(false); /* unreachable */
return 0;
}
}
static int64_t convert_time_unit(int64_t value, TimeUnit unit_from,
TimeUnit unit_to)
{
int power = time_unit_to_power(unit_from) -
time_unit_to_power(unit_to);
while (power < 0) {
value /= 10;
power += 1;
}
while (power > 0) {
value *= 10;
power -= 1;
}
return value;
}
static struct DirtyRateInfo *
query_dirty_rate_info(TimeUnit calc_time_unit)
{ {
int i; int i;
int64_t dirty_rate = DirtyStat.dirty_rate; int64_t dirty_rate = DirtyStat.dirty_rate;
@ -225,7 +256,10 @@ static struct DirtyRateInfo *query_dirty_rate_info(void)
info->status = CalculatingState; info->status = CalculatingState;
info->start_time = DirtyStat.start_time; info->start_time = DirtyStat.start_time;
info->calc_time = DirtyStat.calc_time; info->calc_time = convert_time_unit(DirtyStat.calc_time_ms,
TIME_UNIT_MILLISECOND,
calc_time_unit);
info->calc_time_unit = calc_time_unit;
info->sample_pages = DirtyStat.sample_pages; info->sample_pages = DirtyStat.sample_pages;
info->mode = dirtyrate_mode; info->mode = dirtyrate_mode;
@ -259,12 +293,11 @@ static struct DirtyRateInfo *query_dirty_rate_info(void)
return info; return info;
} }
static void init_dirtyrate_stat(int64_t start_time, static void init_dirtyrate_stat(struct DirtyRateConfig config)
struct DirtyRateConfig config)
{ {
DirtyStat.dirty_rate = -1; DirtyStat.dirty_rate = -1;
DirtyStat.start_time = start_time; DirtyStat.start_time = qemu_clock_get_ms(QEMU_CLOCK_HOST) / 1000;
DirtyStat.calc_time = config.sample_period_seconds; DirtyStat.calc_time_ms = config.calc_time_ms;
DirtyStat.sample_pages = config.sample_pages_per_gigabytes; DirtyStat.sample_pages = config.sample_pages_per_gigabytes;
switch (config.mode) { switch (config.mode) {
@ -574,7 +607,6 @@ static inline void dirtyrate_manual_reset_protect(void)
static void calculate_dirtyrate_dirty_bitmap(struct DirtyRateConfig config) static void calculate_dirtyrate_dirty_bitmap(struct DirtyRateConfig config)
{ {
int64_t msec = 0;
int64_t start_time; int64_t start_time;
DirtyPageRecord dirty_pages; DirtyPageRecord dirty_pages;
@ -600,11 +632,9 @@ static void calculate_dirtyrate_dirty_bitmap(struct DirtyRateConfig config)
record_dirtypages_bitmap(&dirty_pages, true); record_dirtypages_bitmap(&dirty_pages, true);
start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
DirtyStat.start_time = start_time / 1000; DirtyStat.start_time = qemu_clock_get_ms(QEMU_CLOCK_HOST) / 1000;
msec = config.sample_period_seconds * 1000; DirtyStat.calc_time_ms = dirty_stat_wait(config.calc_time_ms, start_time);
msec = dirty_stat_wait(msec, start_time);
DirtyStat.calc_time = msec / 1000;
/* /*
* do two things. * do two things.
@ -615,12 +645,12 @@ static void calculate_dirtyrate_dirty_bitmap(struct DirtyRateConfig config)
record_dirtypages_bitmap(&dirty_pages, false); record_dirtypages_bitmap(&dirty_pages, false);
DirtyStat.dirty_rate = do_calculate_dirtyrate(dirty_pages, msec); DirtyStat.dirty_rate = do_calculate_dirtyrate(dirty_pages,
DirtyStat.calc_time_ms);
} }
static void calculate_dirtyrate_dirty_ring(struct DirtyRateConfig config) static void calculate_dirtyrate_dirty_ring(struct DirtyRateConfig config)
{ {
int64_t duration;
uint64_t dirtyrate = 0; uint64_t dirtyrate = 0;
uint64_t dirtyrate_sum = 0; uint64_t dirtyrate_sum = 0;
int i = 0; int i = 0;
@ -628,15 +658,13 @@ static void calculate_dirtyrate_dirty_ring(struct DirtyRateConfig config)
/* start log sync */ /* start log sync */
global_dirty_log_change(GLOBAL_DIRTY_DIRTY_RATE, true); global_dirty_log_change(GLOBAL_DIRTY_DIRTY_RATE, true);
DirtyStat.start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) / 1000; DirtyStat.start_time = qemu_clock_get_ms(QEMU_CLOCK_HOST) / 1000;
/* calculate vcpu dirtyrate */ /* calculate vcpu dirtyrate */
duration = vcpu_calculate_dirtyrate(config.sample_period_seconds * 1000, DirtyStat.calc_time_ms = vcpu_calculate_dirtyrate(config.calc_time_ms,
&DirtyStat.dirty_ring, &DirtyStat.dirty_ring,
GLOBAL_DIRTY_DIRTY_RATE, GLOBAL_DIRTY_DIRTY_RATE,
true); true);
DirtyStat.calc_time = duration / 1000;
/* calculate vm dirtyrate */ /* calculate vm dirtyrate */
for (i = 0; i < DirtyStat.dirty_ring.nvcpu; i++) { for (i = 0; i < DirtyStat.dirty_ring.nvcpu; i++) {
@ -652,27 +680,25 @@ static void calculate_dirtyrate_sample_vm(struct DirtyRateConfig config)
{ {
struct RamblockDirtyInfo *block_dinfo = NULL; struct RamblockDirtyInfo *block_dinfo = NULL;
int block_count = 0; int block_count = 0;
int64_t msec = 0;
int64_t initial_time; int64_t initial_time;
rcu_read_lock(); rcu_read_lock();
initial_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); initial_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
DirtyStat.start_time = qemu_clock_get_ms(QEMU_CLOCK_HOST) / 1000;
if (!record_ramblock_hash_info(&block_dinfo, config, &block_count)) { if (!record_ramblock_hash_info(&block_dinfo, config, &block_count)) {
goto out; goto out;
} }
rcu_read_unlock(); rcu_read_unlock();
msec = config.sample_period_seconds * 1000; DirtyStat.calc_time_ms = dirty_stat_wait(config.calc_time_ms,
msec = dirty_stat_wait(msec, initial_time); initial_time);
DirtyStat.start_time = initial_time / 1000;
DirtyStat.calc_time = msec / 1000;
rcu_read_lock(); rcu_read_lock();
if (!compare_page_hash_info(block_dinfo, block_count)) { if (!compare_page_hash_info(block_dinfo, block_count)) {
goto out; goto out;
} }
update_dirtyrate(msec); update_dirtyrate(DirtyStat.calc_time_ms);
out: out:
rcu_read_unlock(); rcu_read_unlock();
@ -718,6 +744,8 @@ void *get_dirtyrate_thread(void *arg)
} }
void qmp_calc_dirty_rate(int64_t calc_time, void qmp_calc_dirty_rate(int64_t calc_time,
bool has_calc_time_unit,
TimeUnit calc_time_unit,
bool has_sample_pages, bool has_sample_pages,
int64_t sample_pages, int64_t sample_pages,
bool has_mode, bool has_mode,
@ -727,7 +755,6 @@ void qmp_calc_dirty_rate(int64_t calc_time,
static struct DirtyRateConfig config; static struct DirtyRateConfig config;
QemuThread thread; QemuThread thread;
int ret; int ret;
int64_t start_time;
/* /*
* If the dirty rate is already being measured, don't attempt to start. * If the dirty rate is already being measured, don't attempt to start.
@ -737,10 +764,15 @@ void qmp_calc_dirty_rate(int64_t calc_time,
return; return;
} }
if (!is_sample_period_valid(calc_time)) { int64_t calc_time_ms = convert_time_unit(
error_setg(errp, "calc-time is out of range[%d, %d].", calc_time,
MIN_FETCH_DIRTYRATE_TIME_SEC, has_calc_time_unit ? calc_time_unit : TIME_UNIT_SECOND,
MAX_FETCH_DIRTYRATE_TIME_SEC); TIME_UNIT_MILLISECOND
);
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; return;
} }
@ -787,7 +819,7 @@ void qmp_calc_dirty_rate(int64_t calc_time,
return; return;
} }
config.sample_period_seconds = calc_time; config.calc_time_ms = calc_time_ms;
config.sample_pages_per_gigabytes = sample_pages; config.sample_pages_per_gigabytes = sample_pages;
config.mode = mode; config.mode = mode;
@ -799,21 +831,24 @@ void qmp_calc_dirty_rate(int64_t calc_time,
**/ **/
dirtyrate_mode = mode; dirtyrate_mode = mode;
start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) / 1000; init_dirtyrate_stat(config);
init_dirtyrate_stat(start_time, config);
qemu_thread_create(&thread, "get_dirtyrate", get_dirtyrate_thread, qemu_thread_create(&thread, "get_dirtyrate", get_dirtyrate_thread,
(void *)&config, QEMU_THREAD_DETACHED); (void *)&config, QEMU_THREAD_DETACHED);
} }
struct DirtyRateInfo *qmp_query_dirty_rate(Error **errp)
struct DirtyRateInfo *qmp_query_dirty_rate(bool has_calc_time_unit,
TimeUnit calc_time_unit,
Error **errp)
{ {
return query_dirty_rate_info(); return query_dirty_rate_info(
has_calc_time_unit ? calc_time_unit : TIME_UNIT_SECOND);
} }
void hmp_info_dirty_rate(Monitor *mon, const QDict *qdict) void hmp_info_dirty_rate(Monitor *mon, const QDict *qdict)
{ {
DirtyRateInfo *info = query_dirty_rate_info(); DirtyRateInfo *info = query_dirty_rate_info(TIME_UNIT_SECOND);
monitor_printf(mon, "Status: %s\n", monitor_printf(mon, "Status: %s\n",
DirtyRateStatus_str(info->status)); DirtyRateStatus_str(info->status));
@ -873,8 +908,11 @@ void hmp_calc_dirty_rate(Monitor *mon, const QDict *qdict)
mode = DIRTY_RATE_MEASURE_MODE_DIRTY_RING; mode = DIRTY_RATE_MEASURE_MODE_DIRTY_RING;
} }
qmp_calc_dirty_rate(sec, has_sample_pages, sample_pages, true, qmp_calc_dirty_rate(sec, /* calc-time */
mode, &err); false, TIME_UNIT_SECOND, /* calc-time-unit */
has_sample_pages, sample_pages,
true, mode,
&err);
if (err) { if (err) {
hmp_handle_error(mon, err); hmp_handle_error(mon, err);
return; return;

View File

@ -31,10 +31,12 @@
#define MIN_RAMBLOCK_SIZE 128 #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 MIN_CALC_TIME_MS 50
#define MAX_FETCH_DIRTYRATE_TIME_SEC 60 #define MAX_CALC_TIME_MS 60000
/* /*
* Take 1/16 pages in 1G as the maxmum sample page count * Take 1/16 pages in 1G as the maxmum sample page count
@ -44,7 +46,7 @@
struct DirtyRateConfig { struct DirtyRateConfig {
uint64_t sample_pages_per_gigabytes; /* sample pages per GB */ 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 */ DirtyRateMeasureMode mode; /* mode of dirtyrate measurement */
}; };
@ -73,7 +75,7 @@ typedef struct SampleVMStat {
struct DirtyRateStat { struct DirtyRateStat {
int64_t dirty_rate; /* dirty rate in MB/s */ int64_t dirty_rate; /* dirty rate in MB/s */
int64_t start_time; /* calculation start time in units of second */ 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 */ uint64_t sample_pages; /* sample pages per GB */
union { union {
SampleVMStat page_sampling; SampleVMStat page_sampling;

View File

@ -1836,6 +1836,21 @@
{ 'enum': 'DirtyRateMeasureMode', { 'enum': 'DirtyRateMeasureMode',
'data': ['page-sampling', 'dirty-ring', 'dirty-bitmap'] } 'data': ['page-sampling', 'dirty-ring', 'dirty-bitmap'] }
##
# @TimeUnit:
#
# Specifies unit in which time-related value is specified.
#
# @second: value is in seconds
#
# @millisecond: value is in milliseconds
#
# Since 8.2
#
##
{ 'enum': 'TimeUnit',
'data': ['second', 'millisecond'] }
## ##
# @DirtyRateInfo: # @DirtyRateInfo:
# #
@ -1848,8 +1863,10 @@
# #
# @start-time: start time in units of second for calculation # @start-time: start time in units of second for calculation
# #
# @calc-time: time period for which dirty page rate was measured # @calc-time: time period for which dirty page rate was measured,
# (in seconds) # expressed and rounded down to @calc-time-unit.
#
# @calc-time-unit: time unit of @calc-time (Since 8.2)
# #
# @sample-pages: number of sampled pages per GiB of guest memory. # @sample-pages: number of sampled pages per GiB of guest memory.
# Valid only in page-sampling mode (Since 6.1) # Valid only in page-sampling mode (Since 6.1)
@ -1866,6 +1883,7 @@
'status': 'DirtyRateStatus', 'status': 'DirtyRateStatus',
'start-time': 'int64', 'start-time': 'int64',
'calc-time': 'int64', 'calc-time': 'int64',
'calc-time-unit': 'TimeUnit',
'sample-pages': 'uint64', 'sample-pages': 'uint64',
'mode': 'DirtyRateMeasureMode', 'mode': 'DirtyRateMeasureMode',
'*vcpu-dirty-rate': [ 'DirtyRateVcpu' ] } } '*vcpu-dirty-rate': [ 'DirtyRateVcpu' ] } }
@ -1901,12 +1919,16 @@
# This mode tracks page modification per each vCPU separately. It # This mode tracks page modification per each vCPU separately. It
# requires that KVM accelerator property "dirty-ring-size" is set. # requires that KVM accelerator property "dirty-ring-size" is set.
# #
# @calc-time: time period in units of second for which dirty page rate # @calc-time: time period for which dirty page rate is calculated.
# is calculated. Note that larger @calc-time values will # By default it is specified in seconds, but the unit can be set
# typically result in smaller dirty page rates because page # explicitly with @calc-time-unit. Note that larger @calc-time
# dirtying is a one-time event. Once some page is counted as # values will typically result in smaller dirty page rates because
# dirty during @calc-time period, further writes to this page will # page dirtying is a one-time event. Once some page is counted
# not increase dirty page rate anymore. # as dirty during @calc-time period, further writes to this page
# will not increase dirty page rate anymore.
#
# @calc-time-unit: time unit in which @calc-time is specified.
# By default it is seconds. (Since 8.2)
# #
# @sample-pages: number of sampled pages per each GiB of guest memory. # @sample-pages: number of sampled pages per each GiB of guest memory.
# Default value is 512. For 4KiB guest pages this corresponds to # Default value is 512. For 4KiB guest pages this corresponds to
@ -1924,8 +1946,16 @@
# -> {"execute": "calc-dirty-rate", "arguments": {"calc-time": 1, # -> {"execute": "calc-dirty-rate", "arguments": {"calc-time": 1,
# 'sample-pages': 512} } # 'sample-pages': 512} }
# <- { "return": {} } # <- { "return": {} }
#
# Measure dirty rate using dirty bitmap for 500 milliseconds:
#
# -> {"execute": "calc-dirty-rate", "arguments": {"calc-time": 500,
# "calc-time-unit": "millisecond", "mode": "dirty-bitmap"} }
#
# <- { "return": {} }
## ##
{ 'command': 'calc-dirty-rate', 'data': {'calc-time': 'int64', { 'command': 'calc-dirty-rate', 'data': {'calc-time': 'int64',
'*calc-time-unit': 'TimeUnit',
'*sample-pages': 'int', '*sample-pages': 'int',
'*mode': 'DirtyRateMeasureMode'} } '*mode': 'DirtyRateMeasureMode'} }
@ -1934,6 +1964,9 @@
# #
# Query results of the most recent invocation of @calc-dirty-rate. # Query results of the most recent invocation of @calc-dirty-rate.
# #
# @calc-time-unit: time unit in which to report calculation time.
# By default it is reported in seconds. (Since 8.2)
#
# Since: 5.2 # Since: 5.2
# #
# Examples: # Examples:
@ -1941,14 +1974,17 @@
# 1. Measurement is in progress: # 1. Measurement is in progress:
# #
# <- {"status": "measuring", "sample-pages": 512, # <- {"status": "measuring", "sample-pages": 512,
# "mode": "page-sampling", "start-time": 3665220, "calc-time": 10} # "mode": "page-sampling", "start-time": 1693900454, "calc-time": 10,
# "calc-time-unit": "second"}
# #
# 2. Measurement has been completed: # 2. Measurement has been completed:
# #
# <- {"status": "measured", "sample-pages": 512, "dirty-rate": 108, # <- {"status": "measured", "sample-pages": 512, "dirty-rate": 108,
# "mode": "page-sampling", "start-time": 3665220, "calc-time": 10} # "mode": "page-sampling", "start-time": 1693900454, "calc-time": 10,
# "calc-time-unit": "second"}
## ##
{ 'command': 'query-dirty-rate', 'returns': 'DirtyRateInfo' } { 'command': 'query-dirty-rate', 'data': {'*calc-time-unit': 'TimeUnit' },
'returns': 'DirtyRateInfo' }
## ##
# @DirtyLimitInfo: # @DirtyLimitInfo: