2020-10-28 02:37:33 +03:00
// SPDX-License-Identifier: GPL-2.0
/*
* KVM dirty page logging performance test
*
* Based on dirty_log_test . c
*
* Copyright ( C ) 2018 , Red Hat , Inc .
* Copyright ( C ) 2020 , Google , Inc .
*/
# include <stdio.h>
# include <stdlib.h>
# include <time.h>
# include <pthread.h>
# include <linux/bitmap.h>
# include "kvm_util.h"
# include "test_util.h"
2020-12-18 17:17:32 +03:00
# include "perf_test_util.h"
# include "guest_modes.h"
2020-10-28 02:37:33 +03:00
/* How many host loops to run by default (one KVM_GET_DIRTY_LOG for each loop)*/
# define TEST_HOST_LOOP_N 2UL
2020-12-18 17:17:34 +03:00
static int nr_vcpus = 1 ;
static uint64_t guest_percpu_mem_size = DEFAULT_PER_VCPU_MEM_SIZE ;
2020-10-28 02:37:33 +03:00
/* Host variables */
2020-11-13 19:36:49 +03:00
static u64 dirty_log_manual_caps ;
2020-10-28 02:37:33 +03:00
static bool host_quit ;
2021-01-13 00:42:50 +03:00
static int iteration ;
static int vcpu_last_completed_iteration [ KVM_MAX_VCPUS ] ;
2020-10-28 02:37:33 +03:00
static void * vcpu_worker ( void * data )
{
int ret ;
struct kvm_vm * vm = perf_test_args . vm ;
uint64_t pages_count = 0 ;
struct kvm_run * run ;
struct timespec start ;
struct timespec ts_diff ;
struct timespec total = ( struct timespec ) { 0 } ;
struct timespec avg ;
2020-12-18 17:17:34 +03:00
struct perf_test_vcpu_args * vcpu_args = ( struct perf_test_vcpu_args * ) data ;
2020-10-28 02:37:33 +03:00
int vcpu_id = vcpu_args - > vcpu_id ;
vcpu_args_set ( vm , vcpu_id , 1 , vcpu_id ) ;
run = vcpu_state ( vm , vcpu_id ) ;
while ( ! READ_ONCE ( host_quit ) ) {
2021-01-13 00:42:50 +03:00
int current_iteration = READ_ONCE ( iteration ) ;
2020-10-28 02:37:33 +03:00
clock_gettime ( CLOCK_MONOTONIC , & start ) ;
ret = _vcpu_run ( vm , vcpu_id ) ;
2021-01-13 00:42:48 +03:00
ts_diff = timespec_elapsed ( start ) ;
2020-10-28 02:37:33 +03:00
TEST_ASSERT ( ret = = 0 , " vcpu_run failed: %d \n " , ret ) ;
TEST_ASSERT ( get_ucall ( vm , vcpu_id , NULL ) = = UCALL_SYNC ,
" Invalid guest sync status: exit_reason=%s \n " ,
exit_reason_str ( run - > exit_reason ) ) ;
pr_debug ( " Got sync event from vCPU %d \n " , vcpu_id ) ;
vcpu_last_completed_iteration [ vcpu_id ] = current_iteration ;
2021-01-13 00:42:50 +03:00
pr_debug ( " vCPU %d updated last completed iteration to %d \n " ,
2020-10-28 02:37:33 +03:00
vcpu_id , vcpu_last_completed_iteration [ vcpu_id ] ) ;
if ( current_iteration ) {
pages_count + = vcpu_args - > pages ;
total = timespec_add ( total , ts_diff ) ;
2021-01-13 00:42:50 +03:00
pr_debug ( " vCPU %d iteration %d dirty memory time: %ld.%.9lds \n " ,
2020-10-28 02:37:33 +03:00
vcpu_id , current_iteration , ts_diff . tv_sec ,
ts_diff . tv_nsec ) ;
} else {
2021-01-13 00:42:50 +03:00
pr_debug ( " vCPU %d iteration %d populate memory time: %ld.%.9lds \n " ,
2020-10-28 02:37:33 +03:00
vcpu_id , current_iteration , ts_diff . tv_sec ,
ts_diff . tv_nsec ) ;
}
while ( current_iteration = = READ_ONCE ( iteration ) & &
! READ_ONCE ( host_quit ) ) { }
}
avg = timespec_div ( total , vcpu_last_completed_iteration [ vcpu_id ] ) ;
2021-01-13 00:42:50 +03:00
pr_debug ( " \n vCPU %d dirtied 0x%lx pages over %d iterations in %ld.%.9lds. (Avg %ld.%.9lds/iteration) \n " ,
2020-10-28 02:37:33 +03:00
vcpu_id , pages_count , vcpu_last_completed_iteration [ vcpu_id ] ,
total . tv_sec , total . tv_nsec , avg . tv_sec , avg . tv_nsec ) ;
return NULL ;
}
2020-12-18 17:17:32 +03:00
struct test_params {
unsigned long iterations ;
uint64_t phys_offset ;
int wr_fract ;
} ;
static void run_test ( enum vm_guest_mode mode , void * arg )
2020-10-28 02:37:33 +03:00
{
2020-12-18 17:17:32 +03:00
struct test_params * p = arg ;
2020-10-28 02:37:33 +03:00
pthread_t * vcpu_threads ;
struct kvm_vm * vm ;
unsigned long * bmap ;
uint64_t guest_num_pages ;
uint64_t host_num_pages ;
int vcpu_id ;
struct timespec start ;
struct timespec ts_diff ;
struct timespec get_dirty_log_total = ( struct timespec ) { 0 } ;
struct timespec vcpu_dirty_total = ( struct timespec ) { 0 } ;
struct timespec avg ;
struct kvm_enable_cap cap = { } ;
struct timespec clear_dirty_log_total = ( struct timespec ) { 0 } ;
2020-12-18 17:17:34 +03:00
vm = perf_test_create_vm ( mode , nr_vcpus , guest_percpu_mem_size ) ;
2020-10-28 02:37:33 +03:00
2020-12-18 17:17:32 +03:00
perf_test_args . wr_fract = p - > wr_fract ;
2020-10-28 02:37:33 +03:00
guest_num_pages = ( nr_vcpus * guest_percpu_mem_size ) > > vm_get_page_shift ( vm ) ;
guest_num_pages = vm_adjust_num_guest_pages ( mode , guest_num_pages ) ;
host_num_pages = vm_num_host_pages ( mode , guest_num_pages ) ;
bmap = bitmap_alloc ( host_num_pages ) ;
2020-11-13 19:36:49 +03:00
if ( dirty_log_manual_caps ) {
cap . cap = KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2 ;
cap . args [ 0 ] = dirty_log_manual_caps ;
vm_enable_cap ( vm , & cap ) ;
}
2020-10-28 02:37:33 +03:00
vcpu_threads = malloc ( nr_vcpus * sizeof ( * vcpu_threads ) ) ;
TEST_ASSERT ( vcpu_threads , " Memory allocation failed " ) ;
2020-12-18 17:17:34 +03:00
perf_test_setup_vcpus ( vm , nr_vcpus , guest_percpu_mem_size ) ;
2020-10-28 02:37:33 +03:00
sync_global_to_guest ( vm , perf_test_args ) ;
/* Start the iterations */
iteration = 0 ;
host_quit = false ;
clock_gettime ( CLOCK_MONOTONIC , & start ) ;
for ( vcpu_id = 0 ; vcpu_id < nr_vcpus ; vcpu_id + + ) {
2021-01-13 00:42:51 +03:00
vcpu_last_completed_iteration [ vcpu_id ] = - 1 ;
2020-10-28 02:37:33 +03:00
pthread_create ( & vcpu_threads [ vcpu_id ] , NULL , vcpu_worker ,
& perf_test_args . vcpu_args [ vcpu_id ] ) ;
}
2021-01-13 00:42:51 +03:00
/* Allow the vCPUs to populate memory */
2021-01-13 00:42:50 +03:00
pr_debug ( " Starting iteration %d - Populating \n " , iteration ) ;
2021-01-13 00:42:51 +03:00
for ( vcpu_id = 0 ; vcpu_id < nr_vcpus ; vcpu_id + + ) {
while ( READ_ONCE ( vcpu_last_completed_iteration [ vcpu_id ] ) ! =
iteration )
;
}
2020-10-28 02:37:33 +03:00
2021-01-13 00:42:48 +03:00
ts_diff = timespec_elapsed ( start ) ;
2020-10-28 02:37:33 +03:00
pr_info ( " Populate memory time: %ld.%.9lds \n " ,
ts_diff . tv_sec , ts_diff . tv_nsec ) ;
/* Enable dirty logging */
clock_gettime ( CLOCK_MONOTONIC , & start ) ;
2020-12-18 17:17:34 +03:00
vm_mem_region_set_flags ( vm , PERF_TEST_MEM_SLOT_INDEX ,
2020-10-28 02:37:33 +03:00
KVM_MEM_LOG_DIRTY_PAGES ) ;
2021-01-13 00:42:48 +03:00
ts_diff = timespec_elapsed ( start ) ;
2020-10-28 02:37:33 +03:00
pr_info ( " Enabling dirty logging time: %ld.%.9lds \n \n " ,
ts_diff . tv_sec , ts_diff . tv_nsec ) ;
2020-12-18 17:17:32 +03:00
while ( iteration < p - > iterations ) {
2020-10-28 02:37:33 +03:00
/*
* Incrementing the iteration number will start the vCPUs
* dirtying memory again .
*/
clock_gettime ( CLOCK_MONOTONIC , & start ) ;
iteration + + ;
2021-01-13 00:42:50 +03:00
pr_debug ( " Starting iteration %d \n " , iteration ) ;
2020-10-28 02:37:33 +03:00
for ( vcpu_id = 0 ; vcpu_id < nr_vcpus ; vcpu_id + + ) {
2021-01-13 00:42:49 +03:00
while ( READ_ONCE ( vcpu_last_completed_iteration [ vcpu_id ] )
! = iteration )
;
2020-10-28 02:37:33 +03:00
}
2021-01-13 00:42:48 +03:00
ts_diff = timespec_elapsed ( start ) ;
2020-10-28 02:37:33 +03:00
vcpu_dirty_total = timespec_add ( vcpu_dirty_total , ts_diff ) ;
2021-01-13 00:42:50 +03:00
pr_info ( " Iteration %d dirty memory time: %ld.%.9lds \n " ,
2020-10-28 02:37:33 +03:00
iteration , ts_diff . tv_sec , ts_diff . tv_nsec ) ;
clock_gettime ( CLOCK_MONOTONIC , & start ) ;
2020-12-18 17:17:34 +03:00
kvm_vm_get_dirty_log ( vm , PERF_TEST_MEM_SLOT_INDEX , bmap ) ;
2020-10-28 02:37:33 +03:00
2021-01-13 00:42:48 +03:00
ts_diff = timespec_elapsed ( start ) ;
2020-10-28 02:37:33 +03:00
get_dirty_log_total = timespec_add ( get_dirty_log_total ,
ts_diff ) ;
2021-01-13 00:42:50 +03:00
pr_info ( " Iteration %d get dirty log time: %ld.%.9lds \n " ,
2020-10-28 02:37:33 +03:00
iteration , ts_diff . tv_sec , ts_diff . tv_nsec ) ;
2020-11-13 19:36:49 +03:00
if ( dirty_log_manual_caps ) {
clock_gettime ( CLOCK_MONOTONIC , & start ) ;
2020-12-18 17:17:34 +03:00
kvm_vm_clear_dirty_log ( vm , PERF_TEST_MEM_SLOT_INDEX , bmap , 0 ,
2020-11-13 19:36:49 +03:00
host_num_pages ) ;
2020-10-28 02:37:33 +03:00
2021-01-13 00:42:48 +03:00
ts_diff = timespec_elapsed ( start ) ;
2020-11-13 19:36:49 +03:00
clear_dirty_log_total = timespec_add ( clear_dirty_log_total ,
ts_diff ) ;
2021-01-13 00:42:50 +03:00
pr_info ( " Iteration %d clear dirty log time: %ld.%.9lds \n " ,
2020-11-13 19:36:49 +03:00
iteration , ts_diff . tv_sec , ts_diff . tv_nsec ) ;
}
2020-10-28 02:37:33 +03:00
}
/* Tell the vcpu thread to quit */
host_quit = true ;
for ( vcpu_id = 0 ; vcpu_id < nr_vcpus ; vcpu_id + + )
pthread_join ( vcpu_threads [ vcpu_id ] , NULL ) ;
/* Disable dirty logging */
clock_gettime ( CLOCK_MONOTONIC , & start ) ;
2020-12-18 17:17:34 +03:00
vm_mem_region_set_flags ( vm , PERF_TEST_MEM_SLOT_INDEX , 0 ) ;
2021-01-13 00:42:48 +03:00
ts_diff = timespec_elapsed ( start ) ;
2020-10-28 02:37:33 +03:00
pr_info ( " Disabling dirty logging time: %ld.%.9lds \n " ,
ts_diff . tv_sec , ts_diff . tv_nsec ) ;
2020-12-18 17:17:32 +03:00
avg = timespec_div ( get_dirty_log_total , p - > iterations ) ;
2020-10-28 02:37:33 +03:00
pr_info ( " Get dirty log over %lu iterations took %ld.%.9lds. (Avg %ld.%.9lds/iteration) \n " ,
2020-12-18 17:17:32 +03:00
p - > iterations , get_dirty_log_total . tv_sec ,
2020-10-28 02:37:33 +03:00
get_dirty_log_total . tv_nsec , avg . tv_sec , avg . tv_nsec ) ;
2020-11-13 19:36:49 +03:00
if ( dirty_log_manual_caps ) {
2020-12-18 17:17:32 +03:00
avg = timespec_div ( clear_dirty_log_total , p - > iterations ) ;
2020-11-13 19:36:49 +03:00
pr_info ( " Clear dirty log over %lu iterations took %ld.%.9lds. (Avg %ld.%.9lds/iteration) \n " ,
2020-12-18 17:17:32 +03:00
p - > iterations , clear_dirty_log_total . tv_sec ,
2020-11-13 19:36:49 +03:00
clear_dirty_log_total . tv_nsec , avg . tv_sec , avg . tv_nsec ) ;
}
2020-10-28 02:37:33 +03:00
free ( bmap ) ;
free ( vcpu_threads ) ;
2020-12-18 17:17:34 +03:00
perf_test_destroy_vm ( vm ) ;
2020-10-28 02:37:33 +03:00
}
static void help ( char * name )
{
puts ( " " ) ;
printf ( " usage: %s [-h] [-i iterations] [-p offset] "
" [-m mode] [-b vcpu bytes] [-v vcpus] \n " , name ) ;
puts ( " " ) ;
printf ( " -i: specify iteration counts (default: % " PRIu64 " ) \n " ,
TEST_HOST_LOOP_N ) ;
printf ( " -p: specify guest physical test memory offset \n "
" Warning: a low offset can conflict with the loaded test code. \n " ) ;
2020-12-18 17:17:32 +03:00
guest_modes_help ( ) ;
2020-10-28 02:37:33 +03:00
printf ( " -b: specify the size of the memory region which should be \n "
" dirtied by each vCPU. e.g. 10M or 3G. \n "
" (default: 1G) \n " ) ;
printf ( " -f: specify the fraction of pages which should be written to \n "
" as opposed to simply read, in the form \n "
" 1/<fraction of pages to write>. \n "
" (default: 1 i.e. all pages are written to.) \n " ) ;
printf ( " -v: specify the number of vCPUs to run. \n " ) ;
puts ( " " ) ;
exit ( 0 ) ;
}
int main ( int argc , char * argv [ ] )
{
2020-12-18 17:17:32 +03:00
int max_vcpus = kvm_check_cap ( KVM_CAP_MAX_VCPUS ) ;
struct test_params p = {
. iterations = TEST_HOST_LOOP_N ,
. wr_fract = 1 ,
} ;
int opt ;
2020-10-28 02:37:33 +03:00
dirty_log_manual_caps =
kvm_check_cap ( KVM_CAP_MANUAL_DIRTY_LOG_PROTECT2 ) ;
dirty_log_manual_caps & = ( KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE |
KVM_DIRTY_LOG_INITIALLY_SET ) ;
2020-12-18 17:17:32 +03:00
guest_modes_append_default ( ) ;
2020-10-28 02:37:33 +03:00
while ( ( opt = getopt ( argc , argv , " hi:p:m:b:f:v: " ) ) ! = - 1 ) {
switch ( opt ) {
case ' i ' :
2021-01-13 00:42:50 +03:00
p . iterations = atoi ( optarg ) ;
2020-10-28 02:37:33 +03:00
break ;
case ' p ' :
2020-12-18 17:17:32 +03:00
p . phys_offset = strtoull ( optarg , NULL , 0 ) ;
2020-10-28 02:37:33 +03:00
break ;
case ' m ' :
2020-12-18 17:17:32 +03:00
guest_modes_cmdline ( optarg ) ;
2020-10-28 02:37:33 +03:00
break ;
case ' b ' :
guest_percpu_mem_size = parse_size ( optarg ) ;
break ;
case ' f ' :
2020-12-18 17:17:32 +03:00
p . wr_fract = atoi ( optarg ) ;
TEST_ASSERT ( p . wr_fract > = 1 ,
2020-10-28 02:37:33 +03:00
" Write fraction cannot be less than one " ) ;
break ;
case ' v ' :
nr_vcpus = atoi ( optarg ) ;
2020-12-18 17:17:32 +03:00
TEST_ASSERT ( nr_vcpus > 0 & & nr_vcpus < = max_vcpus ,
" Invalid number of vcpus, must be between 1 and %d " , max_vcpus ) ;
2020-10-28 02:37:33 +03:00
break ;
case ' h ' :
default :
help ( argv [ 0 ] ) ;
break ;
}
}
2020-12-18 17:17:32 +03:00
TEST_ASSERT ( p . iterations > = 2 , " The test should have at least two iterations " ) ;
2020-10-28 02:37:33 +03:00
2020-12-18 17:17:32 +03:00
pr_info ( " Test iterations: % " PRIu64 " \n " , p . iterations ) ;
2020-10-28 02:37:33 +03:00
2020-12-18 17:17:32 +03:00
for_each_guest_mode ( run_test , & p ) ;
2020-10-28 02:37:33 +03:00
return 0 ;
}