tests: Add dirty page rate limit test
Add dirty page rate limit test if kernel support dirty ring, The following qmp commands are covered by this test case: "calc-dirty-rate", "query-dirty-rate", "set-vcpu-dirty-limit", "cancel-vcpu-dirty-limit" and "query-vcpu-dirty-limit". Signed-off-by: Hyman Huang(黄勇) <huangy81@chinatelecom.cn> Acked-by: Peter Xu <peterx@redhat.com> Message-Id: <eed5b847a6ef0a9c02a36383dbdd7db367dd1e7e.1656177590.git.huangy81@chinatelecom.cn> Signed-off-by: Dr. David Alan Gilbert <dgilbert@redhat.com>
This commit is contained in:
parent
f3b2e38cfb
commit
8aff6f501d
|
@ -83,6 +83,28 @@ QDict *wait_command(QTestState *who, const char *command, ...)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Execute the qmp command only
|
||||||
|
*/
|
||||||
|
QDict *qmp_command(QTestState *who, const char *command, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
QDict *resp, *ret;
|
||||||
|
|
||||||
|
va_start(ap, command);
|
||||||
|
resp = qtest_vqmp(who, command, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
g_assert(!qdict_haskey(resp, "error"));
|
||||||
|
g_assert(qdict_haskey(resp, "return"));
|
||||||
|
|
||||||
|
ret = qdict_get_qdict(resp, "return");
|
||||||
|
qobject_ref(ret);
|
||||||
|
qobject_unref(resp);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Send QMP command "migrate".
|
* Send QMP command "migrate".
|
||||||
* Arguments are built from @fmt... (formatted like
|
* Arguments are built from @fmt... (formatted like
|
||||||
|
|
|
@ -23,6 +23,8 @@ QDict *wait_command_fd(QTestState *who, int fd, const char *command, ...);
|
||||||
G_GNUC_PRINTF(2, 3)
|
G_GNUC_PRINTF(2, 3)
|
||||||
QDict *wait_command(QTestState *who, const char *command, ...);
|
QDict *wait_command(QTestState *who, const char *command, ...);
|
||||||
|
|
||||||
|
QDict *qmp_command(QTestState *who, const char *command, ...);
|
||||||
|
|
||||||
G_GNUC_PRINTF(3, 4)
|
G_GNUC_PRINTF(3, 4)
|
||||||
void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...);
|
void migrate_qmp(QTestState *who, const char *uri, const char *fmt, ...);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "qapi/qobject-input-visitor.h"
|
#include "qapi/qobject-input-visitor.h"
|
||||||
#include "qapi/qobject-output-visitor.h"
|
#include "qapi/qobject-output-visitor.h"
|
||||||
#include "crypto/tlscredspsk.h"
|
#include "crypto/tlscredspsk.h"
|
||||||
|
#include "qapi/qmp/qlist.h"
|
||||||
|
|
||||||
#include "migration-helpers.h"
|
#include "migration-helpers.h"
|
||||||
#include "tests/migration/migration-test.h"
|
#include "tests/migration/migration-test.h"
|
||||||
|
@ -46,6 +47,12 @@ unsigned start_address;
|
||||||
unsigned end_address;
|
unsigned end_address;
|
||||||
static bool uffd_feature_thread_id;
|
static bool uffd_feature_thread_id;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dirtylimit stop working if dirty page rate error
|
||||||
|
* value less than DIRTYLIMIT_TOLERANCE_RANGE
|
||||||
|
*/
|
||||||
|
#define DIRTYLIMIT_TOLERANCE_RANGE 25 /* MB/s */
|
||||||
|
|
||||||
#if defined(__linux__)
|
#if defined(__linux__)
|
||||||
#include <sys/syscall.h>
|
#include <sys/syscall.h>
|
||||||
#include <sys/vfs.h>
|
#include <sys/vfs.h>
|
||||||
|
@ -2059,6 +2066,253 @@ static void test_multifd_tcp_cancel(void)
|
||||||
test_migrate_end(from, to2, true);
|
test_migrate_end(from, to2, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void calc_dirty_rate(QTestState *who, uint64_t calc_time)
|
||||||
|
{
|
||||||
|
qobject_unref(qmp_command(who,
|
||||||
|
"{ 'execute': 'calc-dirty-rate',"
|
||||||
|
"'arguments': { "
|
||||||
|
"'calc-time': %ld,"
|
||||||
|
"'mode': 'dirty-ring' }}",
|
||||||
|
calc_time));
|
||||||
|
}
|
||||||
|
|
||||||
|
static QDict *query_dirty_rate(QTestState *who)
|
||||||
|
{
|
||||||
|
return qmp_command(who, "{ 'execute': 'query-dirty-rate' }");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dirtylimit_set_all(QTestState *who, uint64_t dirtyrate)
|
||||||
|
{
|
||||||
|
qobject_unref(qmp_command(who,
|
||||||
|
"{ 'execute': 'set-vcpu-dirty-limit',"
|
||||||
|
"'arguments': { "
|
||||||
|
"'dirty-rate': %ld } }",
|
||||||
|
dirtyrate));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cancel_vcpu_dirty_limit(QTestState *who)
|
||||||
|
{
|
||||||
|
qobject_unref(qmp_command(who,
|
||||||
|
"{ 'execute': 'cancel-vcpu-dirty-limit' }"));
|
||||||
|
}
|
||||||
|
|
||||||
|
static QDict *query_vcpu_dirty_limit(QTestState *who)
|
||||||
|
{
|
||||||
|
QDict *rsp;
|
||||||
|
|
||||||
|
rsp = qtest_qmp(who, "{ 'execute': 'query-vcpu-dirty-limit' }");
|
||||||
|
g_assert(!qdict_haskey(rsp, "error"));
|
||||||
|
g_assert(qdict_haskey(rsp, "return"));
|
||||||
|
|
||||||
|
return rsp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool calc_dirtyrate_ready(QTestState *who)
|
||||||
|
{
|
||||||
|
QDict *rsp_return;
|
||||||
|
gchar *status;
|
||||||
|
|
||||||
|
rsp_return = query_dirty_rate(who);
|
||||||
|
g_assert(rsp_return);
|
||||||
|
|
||||||
|
status = g_strdup(qdict_get_str(rsp_return, "status"));
|
||||||
|
g_assert(status);
|
||||||
|
|
||||||
|
return g_strcmp0(status, "measuring");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wait_for_calc_dirtyrate_complete(QTestState *who,
|
||||||
|
int64_t time_s)
|
||||||
|
{
|
||||||
|
int max_try_count = 10000;
|
||||||
|
usleep(time_s * 1000000);
|
||||||
|
|
||||||
|
while (!calc_dirtyrate_ready(who) && max_try_count--) {
|
||||||
|
usleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the timeout with 10 s(max_try_count * 1000us),
|
||||||
|
* if dirtyrate measurement not complete, fail test.
|
||||||
|
*/
|
||||||
|
g_assert_cmpint(max_try_count, !=, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t get_dirty_rate(QTestState *who)
|
||||||
|
{
|
||||||
|
QDict *rsp_return;
|
||||||
|
gchar *status;
|
||||||
|
QList *rates;
|
||||||
|
const QListEntry *entry;
|
||||||
|
QDict *rate;
|
||||||
|
int64_t dirtyrate;
|
||||||
|
|
||||||
|
rsp_return = query_dirty_rate(who);
|
||||||
|
g_assert(rsp_return);
|
||||||
|
|
||||||
|
status = g_strdup(qdict_get_str(rsp_return, "status"));
|
||||||
|
g_assert(status);
|
||||||
|
g_assert_cmpstr(status, ==, "measured");
|
||||||
|
|
||||||
|
rates = qdict_get_qlist(rsp_return, "vcpu-dirty-rate");
|
||||||
|
g_assert(rates && !qlist_empty(rates));
|
||||||
|
|
||||||
|
entry = qlist_first(rates);
|
||||||
|
g_assert(entry);
|
||||||
|
|
||||||
|
rate = qobject_to(QDict, qlist_entry_obj(entry));
|
||||||
|
g_assert(rate);
|
||||||
|
|
||||||
|
dirtyrate = qdict_get_try_int(rate, "dirty-rate", -1);
|
||||||
|
|
||||||
|
qobject_unref(rsp_return);
|
||||||
|
return dirtyrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int64_t get_limit_rate(QTestState *who)
|
||||||
|
{
|
||||||
|
QDict *rsp_return;
|
||||||
|
QList *rates;
|
||||||
|
const QListEntry *entry;
|
||||||
|
QDict *rate;
|
||||||
|
int64_t dirtyrate;
|
||||||
|
|
||||||
|
rsp_return = query_vcpu_dirty_limit(who);
|
||||||
|
g_assert(rsp_return);
|
||||||
|
|
||||||
|
rates = qdict_get_qlist(rsp_return, "return");
|
||||||
|
g_assert(rates && !qlist_empty(rates));
|
||||||
|
|
||||||
|
entry = qlist_first(rates);
|
||||||
|
g_assert(entry);
|
||||||
|
|
||||||
|
rate = qobject_to(QDict, qlist_entry_obj(entry));
|
||||||
|
g_assert(rate);
|
||||||
|
|
||||||
|
dirtyrate = qdict_get_try_int(rate, "limit-rate", -1);
|
||||||
|
|
||||||
|
qobject_unref(rsp_return);
|
||||||
|
return dirtyrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
static QTestState *dirtylimit_start_vm(void)
|
||||||
|
{
|
||||||
|
QTestState *vm = NULL;
|
||||||
|
g_autofree gchar *cmd = NULL;
|
||||||
|
const char *arch = qtest_get_arch();
|
||||||
|
g_autofree char *bootpath = NULL;
|
||||||
|
|
||||||
|
assert((strcmp(arch, "x86_64") == 0));
|
||||||
|
bootpath = g_strdup_printf("%s/bootsect", tmpfs);
|
||||||
|
assert(sizeof(x86_bootsect) == 512);
|
||||||
|
init_bootfile(bootpath, x86_bootsect, sizeof(x86_bootsect));
|
||||||
|
|
||||||
|
cmd = g_strdup_printf("-accel kvm,dirty-ring-size=4096 "
|
||||||
|
"-name dirtylimit-test,debug-threads=on "
|
||||||
|
"-m 150M -smp 1 "
|
||||||
|
"-serial file:%s/vm_serial "
|
||||||
|
"-drive file=%s,format=raw ",
|
||||||
|
tmpfs, bootpath);
|
||||||
|
|
||||||
|
vm = qtest_init(cmd);
|
||||||
|
return vm;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void dirtylimit_stop_vm(QTestState *vm)
|
||||||
|
{
|
||||||
|
qtest_quit(vm);
|
||||||
|
cleanup("bootsect");
|
||||||
|
cleanup("vm_serial");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_vcpu_dirty_limit(void)
|
||||||
|
{
|
||||||
|
QTestState *vm;
|
||||||
|
int64_t origin_rate;
|
||||||
|
int64_t quota_rate;
|
||||||
|
int64_t rate ;
|
||||||
|
int max_try_count = 20;
|
||||||
|
int hit = 0;
|
||||||
|
|
||||||
|
/* Start vm for vcpu dirtylimit test */
|
||||||
|
vm = dirtylimit_start_vm();
|
||||||
|
|
||||||
|
/* Wait for the first serial output from the vm*/
|
||||||
|
wait_for_serial("vm_serial");
|
||||||
|
|
||||||
|
/* Do dirtyrate measurement with calc time equals 1s */
|
||||||
|
calc_dirty_rate(vm, 1);
|
||||||
|
|
||||||
|
/* Sleep calc time and wait for calc dirtyrate complete */
|
||||||
|
wait_for_calc_dirtyrate_complete(vm, 1);
|
||||||
|
|
||||||
|
/* Query original dirty page rate */
|
||||||
|
origin_rate = get_dirty_rate(vm);
|
||||||
|
|
||||||
|
/* VM booted from bootsect should dirty memory steadily */
|
||||||
|
assert(origin_rate != 0);
|
||||||
|
|
||||||
|
/* Setup quota dirty page rate at half of origin */
|
||||||
|
quota_rate = origin_rate / 2;
|
||||||
|
|
||||||
|
/* Set dirtylimit */
|
||||||
|
dirtylimit_set_all(vm, quota_rate);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if set-vcpu-dirty-limit and query-vcpu-dirty-limit
|
||||||
|
* works literally
|
||||||
|
*/
|
||||||
|
g_assert_cmpint(quota_rate, ==, get_limit_rate(vm));
|
||||||
|
|
||||||
|
/* Sleep a bit to check if it take effect */
|
||||||
|
usleep(2000000);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check if dirtylimit take effect realistically, set the
|
||||||
|
* timeout with 20 s(max_try_count * 1s), if dirtylimit
|
||||||
|
* doesn't take effect, fail test.
|
||||||
|
*/
|
||||||
|
while (--max_try_count) {
|
||||||
|
calc_dirty_rate(vm, 1);
|
||||||
|
wait_for_calc_dirtyrate_complete(vm, 1);
|
||||||
|
rate = get_dirty_rate(vm);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Assume hitting if current rate is less
|
||||||
|
* than quota rate (within accepting error)
|
||||||
|
*/
|
||||||
|
if (rate < (quota_rate + DIRTYLIMIT_TOLERANCE_RANGE)) {
|
||||||
|
hit = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_assert_cmpint(hit, ==, 1);
|
||||||
|
|
||||||
|
hit = 0;
|
||||||
|
max_try_count = 20;
|
||||||
|
|
||||||
|
/* Check if dirtylimit cancellation take effect */
|
||||||
|
cancel_vcpu_dirty_limit(vm);
|
||||||
|
while (--max_try_count) {
|
||||||
|
calc_dirty_rate(vm, 1);
|
||||||
|
wait_for_calc_dirtyrate_complete(vm, 1);
|
||||||
|
rate = get_dirty_rate(vm);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Assume dirtylimit be canceled if current rate is
|
||||||
|
* greater than quota rate (within accepting error)
|
||||||
|
*/
|
||||||
|
if (rate > (quota_rate + DIRTYLIMIT_TOLERANCE_RANGE)) {
|
||||||
|
hit = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_assert_cmpint(hit, ==, 1);
|
||||||
|
dirtylimit_stop_vm(vm);
|
||||||
|
}
|
||||||
|
|
||||||
static bool kvm_dirty_ring_supported(void)
|
static bool kvm_dirty_ring_supported(void)
|
||||||
{
|
{
|
||||||
#if defined(__linux__) && defined(HOST_X86_64)
|
#if defined(__linux__) && defined(HOST_X86_64)
|
||||||
|
@ -2204,6 +2458,8 @@ int main(int argc, char **argv)
|
||||||
if (kvm_dirty_ring_supported()) {
|
if (kvm_dirty_ring_supported()) {
|
||||||
qtest_add_func("/migration/dirty_ring",
|
qtest_add_func("/migration/dirty_ring",
|
||||||
test_precopy_unix_dirty_ring);
|
test_precopy_unix_dirty_ring);
|
||||||
|
qtest_add_func("/migration/vcpu_dirty_limit",
|
||||||
|
test_vcpu_dirty_limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = g_test_run();
|
ret = g_test_run();
|
||||||
|
|
Loading…
Reference in New Issue