]>
Commit | Line | Data |
---|---|---|
4240dcee CZ |
1 | /* |
2 | * Dirtyrate implement code | |
3 | * | |
4 | * Copyright (c) 2020 HUAWEI TECHNOLOGIES CO.,LTD. | |
5 | * | |
6 | * Authors: | |
7 | * Chuan Zheng <[email protected]> | |
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 | ||
ba0e519f | 13 | #include <zlib.h> |
4240dcee CZ |
14 | #include "qemu/osdep.h" |
15 | #include "qapi/error.h" | |
16 | #include "cpu.h" | |
17 | #include "qemu/config-file.h" | |
18 | #include "exec/memory.h" | |
19 | #include "exec/ramblock.h" | |
20 | #include "exec/target_page.h" | |
21 | #include "qemu/rcu_queue.h" | |
22 | #include "qapi/qapi-commands-migration.h" | |
23 | #include "migration.h" | |
3ded54b1 | 24 | #include "ram.h" |
4240dcee CZ |
25 | #include "dirtyrate.h" |
26 | ||
7df3aa30 | 27 | static int CalculatingState = DIRTY_RATE_STATUS_UNSTARTED; |
c9a58d71 | 28 | static struct DirtyRateStat DirtyStat; |
7df3aa30 | 29 | |
eca58224 CZ |
30 | static 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 | ||
44 | static 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 | ||
7df3aa30 CZ |
54 | static int dirtyrate_set_state(int *state, int old_state, int new_state) |
55 | { | |
56 | assert(new_state < DIRTY_RATE_STATUS__MAX); | |
57 | if (qatomic_cmpxchg(state, old_state, new_state) == old_state) { | |
58 | return 0; | |
59 | } else { | |
60 | return -1; | |
61 | } | |
62 | } | |
63 | ||
4c437254 CZ |
64 | static struct DirtyRateInfo *query_dirty_rate_info(void) |
65 | { | |
66 | int64_t dirty_rate = DirtyStat.dirty_rate; | |
67 | struct DirtyRateInfo *info = g_malloc0(sizeof(DirtyRateInfo)); | |
68 | ||
69 | if (qatomic_read(&CalculatingState) == DIRTY_RATE_STATUS_MEASURED) { | |
70 | info->dirty_rate = dirty_rate; | |
71 | } else { | |
72 | info->dirty_rate = -1; | |
73 | } | |
74 | ||
75 | info->status = CalculatingState; | |
76 | info->start_time = DirtyStat.start_time; | |
77 | info->calc_time = DirtyStat.calc_time; | |
78 | ||
79 | return info; | |
80 | } | |
81 | ||
c9a58d71 CZ |
82 | static void reset_dirtyrate_stat(void) |
83 | { | |
84 | DirtyStat.total_dirty_samples = 0; | |
85 | DirtyStat.total_sample_count = 0; | |
86 | DirtyStat.total_block_mem_MB = 0; | |
87 | DirtyStat.dirty_rate = -1; | |
88 | DirtyStat.start_time = 0; | |
89 | DirtyStat.calc_time = 0; | |
90 | } | |
91 | ||
92 | static void update_dirtyrate_stat(struct RamblockDirtyInfo *info) | |
93 | { | |
94 | DirtyStat.total_dirty_samples += info->sample_dirty_count; | |
95 | DirtyStat.total_sample_count += info->sample_pages_count; | |
96 | /* size of total pages in MB */ | |
97 | DirtyStat.total_block_mem_MB += (info->ramblock_pages * | |
98 | TARGET_PAGE_SIZE) >> 20; | |
99 | } | |
100 | ||
101 | static void update_dirtyrate(uint64_t msec) | |
102 | { | |
103 | uint64_t dirtyrate; | |
104 | uint64_t total_dirty_samples = DirtyStat.total_dirty_samples; | |
105 | uint64_t total_sample_count = DirtyStat.total_sample_count; | |
106 | uint64_t total_block_mem_MB = DirtyStat.total_block_mem_MB; | |
107 | ||
108 | dirtyrate = total_dirty_samples * total_block_mem_MB * | |
109 | 1000 / (total_sample_count * msec); | |
110 | ||
111 | DirtyStat.dirty_rate = dirtyrate; | |
112 | } | |
7df3aa30 | 113 | |
ba0e519f CZ |
114 | /* |
115 | * get hash result for the sampled memory with length of TARGET_PAGE_SIZE | |
116 | * in ramblock, which starts from ramblock base address. | |
117 | */ | |
118 | static uint32_t get_ramblock_vfn_hash(struct RamblockDirtyInfo *info, | |
119 | uint64_t vfn) | |
120 | { | |
121 | uint32_t crc; | |
122 | ||
123 | crc = crc32(0, (info->ramblock_addr + | |
124 | vfn * TARGET_PAGE_SIZE), TARGET_PAGE_SIZE); | |
125 | ||
126 | return crc; | |
127 | } | |
128 | ||
129 | static bool save_ramblock_hash(struct RamblockDirtyInfo *info) | |
130 | { | |
131 | unsigned int sample_pages_count; | |
132 | int i; | |
133 | GRand *rand; | |
134 | ||
135 | sample_pages_count = info->sample_pages_count; | |
136 | ||
137 | /* ramblock size less than one page, return success to skip this ramblock */ | |
138 | if (unlikely(info->ramblock_pages == 0 || sample_pages_count == 0)) { | |
139 | return true; | |
140 | } | |
141 | ||
142 | info->hash_result = g_try_malloc0_n(sample_pages_count, | |
143 | sizeof(uint32_t)); | |
144 | if (!info->hash_result) { | |
145 | return false; | |
146 | } | |
147 | ||
148 | info->sample_page_vfn = g_try_malloc0_n(sample_pages_count, | |
149 | sizeof(uint64_t)); | |
150 | if (!info->sample_page_vfn) { | |
151 | g_free(info->hash_result); | |
152 | return false; | |
153 | } | |
154 | ||
155 | rand = g_rand_new(); | |
156 | for (i = 0; i < sample_pages_count; i++) { | |
157 | info->sample_page_vfn[i] = g_rand_int_range(rand, 0, | |
158 | info->ramblock_pages - 1); | |
159 | info->hash_result[i] = get_ramblock_vfn_hash(info, | |
160 | info->sample_page_vfn[i]); | |
161 | } | |
162 | g_rand_free(rand); | |
163 | ||
164 | return true; | |
165 | } | |
166 | ||
167 | static void get_ramblock_dirty_info(RAMBlock *block, | |
168 | struct RamblockDirtyInfo *info, | |
169 | struct DirtyRateConfig *config) | |
170 | { | |
171 | uint64_t sample_pages_per_gigabytes = config->sample_pages_per_gigabytes; | |
172 | ||
173 | /* Right shift 30 bits to calc ramblock size in GB */ | |
174 | info->sample_pages_count = (qemu_ram_get_used_length(block) * | |
175 | sample_pages_per_gigabytes) >> 30; | |
176 | /* Right shift TARGET_PAGE_BITS to calc page count */ | |
177 | info->ramblock_pages = qemu_ram_get_used_length(block) >> | |
178 | TARGET_PAGE_BITS; | |
179 | info->ramblock_addr = qemu_ram_get_host_addr(block); | |
180 | strcpy(info->idstr, qemu_ram_get_idstr(block)); | |
181 | } | |
182 | ||
cf0bbb49 CZ |
183 | static void free_ramblock_dirty_info(struct RamblockDirtyInfo *infos, int count) |
184 | { | |
185 | int i; | |
186 | ||
187 | if (!infos) { | |
188 | return; | |
189 | } | |
190 | ||
191 | for (i = 0; i < count; i++) { | |
192 | g_free(infos[i].sample_page_vfn); | |
193 | g_free(infos[i].hash_result); | |
194 | } | |
195 | g_free(infos); | |
196 | } | |
197 | ||
f82583cd CZ |
198 | static bool skip_sample_ramblock(RAMBlock *block) |
199 | { | |
200 | /* | |
201 | * Sample only blocks larger than MIN_RAMBLOCK_SIZE. | |
202 | */ | |
203 | if (qemu_ram_get_used_length(block) < (MIN_RAMBLOCK_SIZE << 10)) { | |
204 | return true; | |
205 | } | |
206 | ||
207 | return false; | |
208 | } | |
209 | ||
ba0e519f CZ |
210 | static bool record_ramblock_hash_info(struct RamblockDirtyInfo **block_dinfo, |
211 | struct DirtyRateConfig config, | |
212 | int *block_count) | |
213 | { | |
214 | struct RamblockDirtyInfo *info = NULL; | |
215 | struct RamblockDirtyInfo *dinfo = NULL; | |
216 | RAMBlock *block = NULL; | |
217 | int total_count = 0; | |
218 | int index = 0; | |
219 | bool ret = false; | |
220 | ||
221 | RAMBLOCK_FOREACH_MIGRATABLE(block) { | |
f82583cd CZ |
222 | if (skip_sample_ramblock(block)) { |
223 | continue; | |
224 | } | |
ba0e519f CZ |
225 | total_count++; |
226 | } | |
227 | ||
228 | dinfo = g_try_malloc0_n(total_count, sizeof(struct RamblockDirtyInfo)); | |
229 | if (dinfo == NULL) { | |
230 | goto out; | |
231 | } | |
232 | ||
233 | RAMBLOCK_FOREACH_MIGRATABLE(block) { | |
f82583cd CZ |
234 | if (skip_sample_ramblock(block)) { |
235 | continue; | |
236 | } | |
ba0e519f CZ |
237 | if (index >= total_count) { |
238 | break; | |
239 | } | |
240 | info = &dinfo[index]; | |
241 | get_ramblock_dirty_info(block, info, &config); | |
242 | if (!save_ramblock_hash(info)) { | |
243 | goto out; | |
244 | } | |
245 | index++; | |
246 | } | |
247 | ret = true; | |
248 | ||
249 | out: | |
250 | *block_count = index; | |
251 | *block_dinfo = dinfo; | |
252 | return ret; | |
253 | } | |
254 | ||
9c04387b CZ |
255 | static void calc_page_dirty_rate(struct RamblockDirtyInfo *info) |
256 | { | |
257 | uint32_t crc; | |
258 | int i; | |
259 | ||
260 | for (i = 0; i < info->sample_pages_count; i++) { | |
261 | crc = get_ramblock_vfn_hash(info, info->sample_page_vfn[i]); | |
262 | if (crc != info->hash_result[i]) { | |
263 | info->sample_dirty_count++; | |
264 | } | |
265 | } | |
266 | } | |
267 | ||
268 | static struct RamblockDirtyInfo * | |
269 | find_block_matched(RAMBlock *block, int count, | |
270 | struct RamblockDirtyInfo *infos) | |
271 | { | |
272 | int i; | |
273 | struct RamblockDirtyInfo *matched; | |
274 | ||
275 | for (i = 0; i < count; i++) { | |
276 | if (!strcmp(infos[i].idstr, qemu_ram_get_idstr(block))) { | |
277 | break; | |
278 | } | |
279 | } | |
280 | ||
281 | if (i == count) { | |
282 | return NULL; | |
283 | } | |
284 | ||
285 | if (infos[i].ramblock_addr != qemu_ram_get_host_addr(block) || | |
286 | infos[i].ramblock_pages != | |
287 | (qemu_ram_get_used_length(block) >> TARGET_PAGE_BITS)) { | |
288 | return NULL; | |
289 | } | |
290 | ||
291 | matched = &infos[i]; | |
292 | ||
293 | return matched; | |
294 | } | |
295 | ||
296 | static bool compare_page_hash_info(struct RamblockDirtyInfo *info, | |
297 | int block_count) | |
298 | { | |
299 | struct RamblockDirtyInfo *block_dinfo = NULL; | |
300 | RAMBlock *block = NULL; | |
301 | ||
302 | RAMBLOCK_FOREACH_MIGRATABLE(block) { | |
f82583cd CZ |
303 | if (skip_sample_ramblock(block)) { |
304 | continue; | |
305 | } | |
9c04387b CZ |
306 | block_dinfo = find_block_matched(block, block_count, info); |
307 | if (block_dinfo == NULL) { | |
308 | continue; | |
309 | } | |
310 | calc_page_dirty_rate(block_dinfo); | |
311 | update_dirtyrate_stat(block_dinfo); | |
312 | } | |
313 | ||
314 | if (DirtyStat.total_sample_count == 0) { | |
315 | return false; | |
316 | } | |
317 | ||
318 | return true; | |
319 | } | |
320 | ||
4240dcee CZ |
321 | static void calculate_dirtyrate(struct DirtyRateConfig config) |
322 | { | |
cf0bbb49 CZ |
323 | struct RamblockDirtyInfo *block_dinfo = NULL; |
324 | int block_count = 0; | |
325 | int64_t msec = 0; | |
326 | int64_t initial_time; | |
327 | ||
328 | rcu_register_thread(); | |
329 | reset_dirtyrate_stat(); | |
330 | rcu_read_lock(); | |
331 | initial_time = qemu_clock_get_ms(QEMU_CLOCK_REALTIME); | |
332 | if (!record_ramblock_hash_info(&block_dinfo, config, &block_count)) { | |
333 | goto out; | |
334 | } | |
335 | rcu_read_unlock(); | |
336 | ||
337 | msec = config.sample_period_seconds * 1000; | |
338 | msec = set_sample_page_period(msec, initial_time); | |
4c437254 CZ |
339 | DirtyStat.start_time = initial_time / 1000; |
340 | DirtyStat.calc_time = msec / 1000; | |
cf0bbb49 CZ |
341 | |
342 | rcu_read_lock(); | |
343 | if (!compare_page_hash_info(block_dinfo, block_count)) { | |
344 | goto out; | |
345 | } | |
346 | ||
347 | update_dirtyrate(msec); | |
348 | ||
349 | out: | |
350 | rcu_read_unlock(); | |
351 | free_ramblock_dirty_info(block_dinfo, block_count); | |
352 | rcu_unregister_thread(); | |
4240dcee CZ |
353 | } |
354 | ||
355 | void *get_dirtyrate_thread(void *arg) | |
356 | { | |
357 | struct DirtyRateConfig config = *(struct DirtyRateConfig *)arg; | |
7df3aa30 CZ |
358 | int ret; |
359 | ||
360 | ret = dirtyrate_set_state(&CalculatingState, DIRTY_RATE_STATUS_UNSTARTED, | |
361 | DIRTY_RATE_STATUS_MEASURING); | |
362 | if (ret == -1) { | |
363 | error_report("change dirtyrate state failed."); | |
364 | return NULL; | |
365 | } | |
4240dcee CZ |
366 | |
367 | calculate_dirtyrate(config); | |
368 | ||
7df3aa30 CZ |
369 | ret = dirtyrate_set_state(&CalculatingState, DIRTY_RATE_STATUS_MEASURING, |
370 | DIRTY_RATE_STATUS_MEASURED); | |
371 | if (ret == -1) { | |
372 | error_report("change dirtyrate state failed."); | |
373 | } | |
4240dcee CZ |
374 | return NULL; |
375 | } | |
4c437254 CZ |
376 | |
377 | void qmp_calc_dirty_rate(int64_t calc_time, Error **errp) | |
378 | { | |
379 | static struct DirtyRateConfig config; | |
380 | QemuThread thread; | |
381 | int ret; | |
382 | ||
383 | /* | |
384 | * If the dirty rate is already being measured, don't attempt to start. | |
385 | */ | |
386 | if (qatomic_read(&CalculatingState) == DIRTY_RATE_STATUS_MEASURING) { | |
387 | error_setg(errp, "the dirty rate is already being measured."); | |
388 | return; | |
389 | } | |
390 | ||
391 | if (!is_sample_period_valid(calc_time)) { | |
392 | error_setg(errp, "calc-time is out of range[%d, %d].", | |
393 | MIN_FETCH_DIRTYRATE_TIME_SEC, | |
394 | MAX_FETCH_DIRTYRATE_TIME_SEC); | |
395 | return; | |
396 | } | |
397 | ||
398 | /* | |
399 | * Init calculation state as unstarted. | |
400 | */ | |
401 | ret = dirtyrate_set_state(&CalculatingState, CalculatingState, | |
402 | DIRTY_RATE_STATUS_UNSTARTED); | |
403 | if (ret == -1) { | |
404 | error_setg(errp, "init dirty rate calculation state failed."); | |
405 | return; | |
406 | } | |
407 | ||
408 | config.sample_period_seconds = calc_time; | |
409 | config.sample_pages_per_gigabytes = DIRTYRATE_DEFAULT_SAMPLE_PAGES; | |
410 | qemu_thread_create(&thread, "get_dirtyrate", get_dirtyrate_thread, | |
411 | (void *)&config, QEMU_THREAD_DETACHED); | |
412 | } | |
413 | ||
414 | struct DirtyRateInfo *qmp_query_dirty_rate(Error **errp) | |
415 | { | |
416 | return query_dirty_rate_info(); | |
417 | } |