dirtyrate.c (14243B)
1/* 2 * Dirtyrate implement code 3 * 4 * Copyright (c) 2020 HUAWEI TECHNOLOGIES CO.,LTD. 5 * 6 * Authors: 7 * Chuan Zheng <zhengchuan@huawei.com> 8 * 9 * This work is licensed under the terms of the GNU GPL, version 2 or later. 10 * See the COPYING file in the top-level directory. 11 */ 12 13#include "qemu/osdep.h" 14#include <zlib.h> 15#include "qapi/error.h" 16#include "cpu.h" 17#include "exec/ramblock.h" 18#include "qemu/rcu_queue.h" 19#include "qapi/qapi-commands-migration.h" 20#include "ram.h" 21#include "trace.h" 22#include "dirtyrate.h" 23#include "monitor/hmp.h" 24#include "monitor/monitor.h" 25#include "qapi/qmp/qdict.h" 26 27static int CalculatingState = DIRTY_RATE_STATUS_UNSTARTED; 28static struct DirtyRateStat DirtyStat; 29 30static int64_t set_sample_page_period(int64_t msec, int64_t initial_time) 31{ 32 int64_t current_time; 33 34 current_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); 35 if ((current_time - initial_time) >= msec) { 36 msec = current_time - initial_time; 37 } else { 38 g_usleep((msec + initial_time - current_time) * 1000); 39 } 40 41 return msec; 42} 43 44static bool is_sample_period_valid(int64_t sec) 45{ 46 if (sec < MIN_FETCH_DIRTYRATE_TIME_SEC || 47 sec > MAX_FETCH_DIRTYRATE_TIME_SEC) { 48 return false; 49 } 50 51 return true; 52} 53 54static bool is_sample_pages_valid(int64_t pages) 55{ 56 return pages >= MIN_SAMPLE_PAGE_COUNT && 57 pages <= MAX_SAMPLE_PAGE_COUNT; 58} 59 60static int dirtyrate_set_state(int *state, int old_state, int new_state) 61{ 62 assert(new_state < DIRTY_RATE_STATUS__MAX); 63 trace_dirtyrate_set_state(DirtyRateStatus_str(new_state)); 64 if (qatomic_cmpxchg(state, old_state, new_state) == old_state) { 65 return 0; 66 } else { 67 return -1; 68 } 69} 70 71static struct DirtyRateInfo *query_dirty_rate_info(void) 72{ 73 int64_t dirty_rate = DirtyStat.dirty_rate; 74 struct DirtyRateInfo *info = g_malloc0(sizeof(DirtyRateInfo)); 75 76 if (qatomic_read(&CalculatingState) == DIRTY_RATE_STATUS_MEASURED) { 77 info->has_dirty_rate = true; 78 info->dirty_rate = dirty_rate; 79 } 80 81 info->status = CalculatingState; 82 info->start_time = DirtyStat.start_time; 83 info->calc_time = DirtyStat.calc_time; 84 info->sample_pages = DirtyStat.sample_pages; 85 86 trace_query_dirty_rate_info(DirtyRateStatus_str(CalculatingState)); 87 88 return info; 89} 90 91static void init_dirtyrate_stat(int64_t start_time, int64_t calc_time, 92 uint64_t sample_pages) 93{ 94 DirtyStat.total_dirty_samples = 0; 95 DirtyStat.total_sample_count = 0; 96 DirtyStat.total_block_mem_MB = 0; 97 DirtyStat.dirty_rate = -1; 98 DirtyStat.start_time = start_time; 99 DirtyStat.calc_time = calc_time; 100 DirtyStat.sample_pages = sample_pages; 101} 102 103static void update_dirtyrate_stat(struct RamblockDirtyInfo *info) 104{ 105 DirtyStat.total_dirty_samples += info->sample_dirty_count; 106 DirtyStat.total_sample_count += info->sample_pages_count; 107 /* size of total pages in MB */ 108 DirtyStat.total_block_mem_MB += (info->ramblock_pages * 109 TARGET_PAGE_SIZE) >> 20; 110} 111 112static void update_dirtyrate(uint64_t msec) 113{ 114 uint64_t dirtyrate; 115 uint64_t total_dirty_samples = DirtyStat.total_dirty_samples; 116 uint64_t total_sample_count = DirtyStat.total_sample_count; 117 uint64_t total_block_mem_MB = DirtyStat.total_block_mem_MB; 118 119 dirtyrate = total_dirty_samples * total_block_mem_MB * 120 1000 / (total_sample_count * msec); 121 122 DirtyStat.dirty_rate = dirtyrate; 123} 124 125/* 126 * get hash result for the sampled memory with length of TARGET_PAGE_SIZE 127 * in ramblock, which starts from ramblock base address. 128 */ 129static uint32_t get_ramblock_vfn_hash(struct RamblockDirtyInfo *info, 130 uint64_t vfn) 131{ 132 uint32_t crc; 133 134 crc = crc32(0, (info->ramblock_addr + 135 vfn * TARGET_PAGE_SIZE), TARGET_PAGE_SIZE); 136 137 trace_get_ramblock_vfn_hash(info->idstr, vfn, crc); 138 return crc; 139} 140 141static bool save_ramblock_hash(struct RamblockDirtyInfo *info) 142{ 143 unsigned int sample_pages_count; 144 int i; 145 GRand *rand; 146 147 sample_pages_count = info->sample_pages_count; 148 149 /* ramblock size less than one page, return success to skip this ramblock */ 150 if (unlikely(info->ramblock_pages == 0 || sample_pages_count == 0)) { 151 return true; 152 } 153 154 info->hash_result = g_try_malloc0_n(sample_pages_count, 155 sizeof(uint32_t)); 156 if (!info->hash_result) { 157 return false; 158 } 159 160 info->sample_page_vfn = g_try_malloc0_n(sample_pages_count, 161 sizeof(uint64_t)); 162 if (!info->sample_page_vfn) { 163 g_free(info->hash_result); 164 return false; 165 } 166 167 rand = g_rand_new(); 168 for (i = 0; i < sample_pages_count; i++) { 169 info->sample_page_vfn[i] = g_rand_int_range(rand, 0, 170 info->ramblock_pages - 1); 171 info->hash_result[i] = get_ramblock_vfn_hash(info, 172 info->sample_page_vfn[i]); 173 } 174 g_rand_free(rand); 175 176 return true; 177} 178 179static void get_ramblock_dirty_info(RAMBlock *block, 180 struct RamblockDirtyInfo *info, 181 struct DirtyRateConfig *config) 182{ 183 uint64_t sample_pages_per_gigabytes = config->sample_pages_per_gigabytes; 184 185 /* Right shift 30 bits to calc ramblock size in GB */ 186 info->sample_pages_count = (qemu_ram_get_used_length(block) * 187 sample_pages_per_gigabytes) >> 30; 188 /* Right shift TARGET_PAGE_BITS to calc page count */ 189 info->ramblock_pages = qemu_ram_get_used_length(block) >> 190 TARGET_PAGE_BITS; 191 info->ramblock_addr = qemu_ram_get_host_addr(block); 192 strcpy(info->idstr, qemu_ram_get_idstr(block)); 193} 194 195static void free_ramblock_dirty_info(struct RamblockDirtyInfo *infos, int count) 196{ 197 int i; 198 199 if (!infos) { 200 return; 201 } 202 203 for (i = 0; i < count; i++) { 204 g_free(infos[i].sample_page_vfn); 205 g_free(infos[i].hash_result); 206 } 207 g_free(infos); 208} 209 210static bool skip_sample_ramblock(RAMBlock *block) 211{ 212 /* 213 * Sample only blocks larger than MIN_RAMBLOCK_SIZE. 214 */ 215 if (qemu_ram_get_used_length(block) < (MIN_RAMBLOCK_SIZE << 10)) { 216 trace_skip_sample_ramblock(block->idstr, 217 qemu_ram_get_used_length(block)); 218 return true; 219 } 220 221 return false; 222} 223 224static bool record_ramblock_hash_info(struct RamblockDirtyInfo **block_dinfo, 225 struct DirtyRateConfig config, 226 int *block_count) 227{ 228 struct RamblockDirtyInfo *info = NULL; 229 struct RamblockDirtyInfo *dinfo = NULL; 230 RAMBlock *block = NULL; 231 int total_count = 0; 232 int index = 0; 233 bool ret = false; 234 235 RAMBLOCK_FOREACH_MIGRATABLE(block) { 236 if (skip_sample_ramblock(block)) { 237 continue; 238 } 239 total_count++; 240 } 241 242 dinfo = g_try_malloc0_n(total_count, sizeof(struct RamblockDirtyInfo)); 243 if (dinfo == NULL) { 244 goto out; 245 } 246 247 RAMBLOCK_FOREACH_MIGRATABLE(block) { 248 if (skip_sample_ramblock(block)) { 249 continue; 250 } 251 if (index >= total_count) { 252 break; 253 } 254 info = &dinfo[index]; 255 get_ramblock_dirty_info(block, info, &config); 256 if (!save_ramblock_hash(info)) { 257 goto out; 258 } 259 index++; 260 } 261 ret = true; 262 263out: 264 *block_count = index; 265 *block_dinfo = dinfo; 266 return ret; 267} 268 269static void calc_page_dirty_rate(struct RamblockDirtyInfo *info) 270{ 271 uint32_t crc; 272 int i; 273 274 for (i = 0; i < info->sample_pages_count; i++) { 275 crc = get_ramblock_vfn_hash(info, info->sample_page_vfn[i]); 276 if (crc != info->hash_result[i]) { 277 trace_calc_page_dirty_rate(info->idstr, crc, info->hash_result[i]); 278 info->sample_dirty_count++; 279 } 280 } 281} 282 283static struct RamblockDirtyInfo * 284find_block_matched(RAMBlock *block, int count, 285 struct RamblockDirtyInfo *infos) 286{ 287 int i; 288 struct RamblockDirtyInfo *matched; 289 290 for (i = 0; i < count; i++) { 291 if (!strcmp(infos[i].idstr, qemu_ram_get_idstr(block))) { 292 break; 293 } 294 } 295 296 if (i == count) { 297 return NULL; 298 } 299 300 if (infos[i].ramblock_addr != qemu_ram_get_host_addr(block) || 301 infos[i].ramblock_pages != 302 (qemu_ram_get_used_length(block) >> TARGET_PAGE_BITS)) { 303 trace_find_page_matched(block->idstr); 304 return NULL; 305 } 306 307 matched = &infos[i]; 308 309 return matched; 310} 311 312static bool compare_page_hash_info(struct RamblockDirtyInfo *info, 313 int block_count) 314{ 315 struct RamblockDirtyInfo *block_dinfo = NULL; 316 RAMBlock *block = NULL; 317 318 RAMBLOCK_FOREACH_MIGRATABLE(block) { 319 if (skip_sample_ramblock(block)) { 320 continue; 321 } 322 block_dinfo = find_block_matched(block, block_count, info); 323 if (block_dinfo == NULL) { 324 continue; 325 } 326 calc_page_dirty_rate(block_dinfo); 327 update_dirtyrate_stat(block_dinfo); 328 } 329 330 if (DirtyStat.total_sample_count == 0) { 331 return false; 332 } 333 334 return true; 335} 336 337static void calculate_dirtyrate(struct DirtyRateConfig config) 338{ 339 struct RamblockDirtyInfo *block_dinfo = NULL; 340 int block_count = 0; 341 int64_t msec = 0; 342 int64_t initial_time; 343 344 rcu_register_thread(); 345 rcu_read_lock(); 346 initial_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); 347 if (!record_ramblock_hash_info(&block_dinfo, config, &block_count)) { 348 goto out; 349 } 350 rcu_read_unlock(); 351 352 msec = config.sample_period_seconds * 1000; 353 msec = set_sample_page_period(msec, initial_time); 354 DirtyStat.start_time = initial_time / 1000; 355 DirtyStat.calc_time = msec / 1000; 356 357 rcu_read_lock(); 358 if (!compare_page_hash_info(block_dinfo, block_count)) { 359 goto out; 360 } 361 362 update_dirtyrate(msec); 363 364out: 365 rcu_read_unlock(); 366 free_ramblock_dirty_info(block_dinfo, block_count); 367 rcu_unregister_thread(); 368} 369 370void *get_dirtyrate_thread(void *arg) 371{ 372 struct DirtyRateConfig config = *(struct DirtyRateConfig *)arg; 373 int ret; 374 int64_t start_time; 375 int64_t calc_time; 376 uint64_t sample_pages; 377 378 ret = dirtyrate_set_state(&CalculatingState, DIRTY_RATE_STATUS_UNSTARTED, 379 DIRTY_RATE_STATUS_MEASURING); 380 if (ret == -1) { 381 error_report("change dirtyrate state failed."); 382 return NULL; 383 } 384 385 start_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME) / 1000; 386 calc_time = config.sample_period_seconds; 387 sample_pages = config.sample_pages_per_gigabytes; 388 init_dirtyrate_stat(start_time, calc_time, sample_pages); 389 390 calculate_dirtyrate(config); 391 392 ret = dirtyrate_set_state(&CalculatingState, DIRTY_RATE_STATUS_MEASURING, 393 DIRTY_RATE_STATUS_MEASURED); 394 if (ret == -1) { 395 error_report("change dirtyrate state failed."); 396 } 397 return NULL; 398} 399 400void qmp_calc_dirty_rate(int64_t calc_time, bool has_sample_pages, 401 int64_t sample_pages, Error **errp) 402{ 403 static struct DirtyRateConfig config; 404 QemuThread thread; 405 int ret; 406 407 /* 408 * If the dirty rate is already being measured, don't attempt to start. 409 */ 410 if (qatomic_read(&CalculatingState) == DIRTY_RATE_STATUS_MEASURING) { 411 error_setg(errp, "the dirty rate is already being measured."); 412 return; 413 } 414 415 if (!is_sample_period_valid(calc_time)) { 416 error_setg(errp, "calc-time is out of range[%d, %d].", 417 MIN_FETCH_DIRTYRATE_TIME_SEC, 418 MAX_FETCH_DIRTYRATE_TIME_SEC); 419 return; 420 } 421 422 if (has_sample_pages) { 423 if (!is_sample_pages_valid(sample_pages)) { 424 error_setg(errp, "sample-pages is out of range[%d, %d].", 425 MIN_SAMPLE_PAGE_COUNT, 426 MAX_SAMPLE_PAGE_COUNT); 427 return; 428 } 429 } else { 430 sample_pages = DIRTYRATE_DEFAULT_SAMPLE_PAGES; 431 } 432 433 /* 434 * Init calculation state as unstarted. 435 */ 436 ret = dirtyrate_set_state(&CalculatingState, CalculatingState, 437 DIRTY_RATE_STATUS_UNSTARTED); 438 if (ret == -1) { 439 error_setg(errp, "init dirty rate calculation state failed."); 440 return; 441 } 442 443 config.sample_period_seconds = calc_time; 444 config.sample_pages_per_gigabytes = sample_pages; 445 qemu_thread_create(&thread, "get_dirtyrate", get_dirtyrate_thread, 446 (void *)&config, QEMU_THREAD_DETACHED); 447} 448 449struct DirtyRateInfo *qmp_query_dirty_rate(Error **errp) 450{ 451 return query_dirty_rate_info(); 452} 453 454void hmp_info_dirty_rate(Monitor *mon, const QDict *qdict) 455{ 456 DirtyRateInfo *info = query_dirty_rate_info(); 457 458 monitor_printf(mon, "Status: %s\n", 459 DirtyRateStatus_str(info->status)); 460 monitor_printf(mon, "Start Time: %"PRIi64" (ms)\n", 461 info->start_time); 462 monitor_printf(mon, "Sample Pages: %"PRIu64" (per GB)\n", 463 info->sample_pages); 464 monitor_printf(mon, "Period: %"PRIi64" (sec)\n", 465 info->calc_time); 466 monitor_printf(mon, "Dirty rate: "); 467 if (info->has_dirty_rate) { 468 monitor_printf(mon, "%"PRIi64" (MB/s)\n", info->dirty_rate); 469 } else { 470 monitor_printf(mon, "(not ready)\n"); 471 } 472 g_free(info); 473} 474 475void hmp_calc_dirty_rate(Monitor *mon, const QDict *qdict) 476{ 477 int64_t sec = qdict_get_try_int(qdict, "second", 0); 478 int64_t sample_pages = qdict_get_try_int(qdict, "sample_pages_per_GB", -1); 479 bool has_sample_pages = (sample_pages != -1); 480 Error *err = NULL; 481 482 if (!sec) { 483 monitor_printf(mon, "Incorrect period length specified!\n"); 484 return; 485 } 486 487 qmp_calc_dirty_rate(sec, has_sample_pages, sample_pages, &err); 488 if (err) { 489 hmp_handle_error(mon, err); 490 return; 491 } 492 493 monitor_printf(mon, "Starting dirty rate measurement with period %"PRIi64 494 " seconds\n", sec); 495 monitor_printf(mon, "[Please use 'info dirty_rate' to check results]\n"); 496}