*/
struct xenevtchn_handle;
+/* For save's precopy_policy(). */
+struct precopy_stats
+{
+ unsigned int iteration;
+ unsigned int total_written;
+ long dirty_count; /* -1 if unknown */
+};
+
+/*
+ * A precopy_policy callback may not be running in the same address
+ * space as libxc an so precopy_stats is passed by value.
+ */
+typedef int (*precopy_policy_t)(struct precopy_stats, void *);
+
/* callbacks provided by xc_domain_save */
struct save_callbacks {
/* Called after expiration of checkpoint interval,
*/
int (*suspend)(void* data);
- /* Called after the guest's dirty pages have been
+ /*
+ * Called before and after every batch of page data sent during
+ * the precopy phase of a live migration to ask the caller what
+ * to do next based on the current state of the precopy migration.
+ *
+ * Should return one of the values listed below:
+ */
+#define XGS_POLICY_ABORT (-1) /* Abandon the migration entirely
+ * and tidy up. */
+#define XGS_POLICY_CONTINUE_PRECOPY 0 /* Remain in the precopy phase. */
+#define XGS_POLICY_STOP_AND_COPY 1 /* Immediately suspend and transmit the
+ * remaining dirty pages. */
+ precopy_policy_t precopy_policy;
+
+ /*
+ * Called after the guest's dirty pages have been
* copied into an output buffer.
* Callback function resumes the guest & the device model,
* returns to xc_domain_save.
*/
int (*postcopy)(void* data);
- /* Called after the memory checkpoint has been flushed
+ /*
+ * Called after the memory checkpoint has been flushed
* out into the network. Typical actions performed in this
* callback include:
* (a) send the saved device model state (for HVM guests),
*
* returns:
* 0: terminate checkpointing gracefully
- * 1: take another checkpoint */
+ * 1: take another checkpoint
+ */
int (*checkpoint)(void* data);
/*
return 0;
}
-static int update_progress_string(struct xc_sr_context *ctx,
- char **str, unsigned iter)
+static int update_progress_string(struct xc_sr_context *ctx, char **str)
{
xc_interface *xch = ctx->xch;
char *new_str = NULL;
+ unsigned int iter = ctx->save.stats.iteration;
- if ( asprintf(&new_str, "Frames iteration %u of %u",
- iter, ctx->save.max_iterations) == -1 )
+ if ( asprintf(&new_str, "Frames iteration %u", iter) == -1 )
{
PERROR("Unable to allocate new progress string");
return -1;
return 0;
}
+/*
+ * This is the live migration precopy policy - it's called periodically during
+ * the precopy phase of live migrations, and is responsible for deciding when
+ * the precopy phase should terminate and what should be done next.
+ *
+ * The policy implemented here behaves identically to the policy previously
+ * hard-coded into xc_domain_save() - it proceeds to the stop-and-copy phase of
+ * the live migration when there are either fewer than 50 dirty pages, or more
+ * than 5 precopy rounds have completed.
+ */
+#define SPP_MAX_ITERATIONS 5
+#define SPP_TARGET_DIRTY_COUNT 50
+
+static int simple_precopy_policy(struct precopy_stats stats, void *user)
+{
+ return ((stats.dirty_count >= 0 &&
+ stats.dirty_count < SPP_TARGET_DIRTY_COUNT) ||
+ stats.iteration >= SPP_MAX_ITERATIONS)
+ ? XGS_POLICY_STOP_AND_COPY
+ : XGS_POLICY_CONTINUE_PRECOPY;
+}
+
/*
* Send memory while guest is running.
*/
xc_interface *xch = ctx->xch;
xc_shadow_op_stats_t stats = { 0, ctx->save.p2m_size };
char *progress_str = NULL;
- unsigned x;
+ unsigned int x = 0;
int rc;
+ int policy_decision;
- rc = update_progress_string(ctx, &progress_str, 0);
- if ( rc )
- goto out;
+ DECLARE_HYPERCALL_BUFFER_SHADOW(unsigned long, dirty_bitmap,
+ &ctx->save.dirty_bitmap_hbuf);
- rc = send_all_pages(ctx);
+ precopy_policy_t precopy_policy = ctx->save.callbacks->precopy_policy;
+ void *data = ctx->save.callbacks->data;
+
+ struct precopy_stats *policy_stats;
+
+ rc = update_progress_string(ctx, &progress_str);
if ( rc )
goto out;
- for ( x = 1;
- ((x < ctx->save.max_iterations) &&
- (stats.dirty_count > ctx->save.dirty_threshold)); ++x )
+ ctx->save.stats = (struct precopy_stats)
+ { .dirty_count = ctx->save.p2m_size };
+ policy_stats = &ctx->save.stats;
+
+ if ( precopy_policy == NULL )
+ precopy_policy = simple_precopy_policy;
+
+ bitmap_set(dirty_bitmap, ctx->save.p2m_size);
+
+ for ( ; ; )
{
+ policy_decision = precopy_policy(*policy_stats, data);
+ x++;
+
+ if ( stats.dirty_count > 0 && policy_decision != XGS_POLICY_ABORT )
+ {
+ rc = update_progress_string(ctx, &progress_str);
+ if ( rc )
+ goto out;
+
+ rc = send_dirty_pages(ctx, stats.dirty_count);
+ if ( rc )
+ goto out;
+ }
+
+ if ( policy_decision != XGS_POLICY_CONTINUE_PRECOPY )
+ break;
+
+ policy_stats->iteration = x;
+ policy_stats->total_written += policy_stats->dirty_count;
+ policy_stats->dirty_count = -1;
+
+ policy_decision = precopy_policy(*policy_stats, data);
+
+ if ( policy_decision != XGS_POLICY_CONTINUE_PRECOPY )
+ break;
+
if ( xc_shadow_control(
xch, ctx->domid, XEN_DOMCTL_SHADOW_OP_CLEAN,
&ctx->save.dirty_bitmap_hbuf, ctx->save.p2m_size,
goto out;
}
- if ( stats.dirty_count == 0 )
- break;
+ policy_stats->dirty_count = stats.dirty_count;
- rc = update_progress_string(ctx, &progress_str, x);
- if ( rc )
- goto out;
-
- rc = send_dirty_pages(ctx, stats.dirty_count);
- if ( rc )
- goto out;
}
out:
if ( ctx->save.live )
{
- rc = update_progress_string(ctx, &progress_str,
- ctx->save.max_iterations);
+ rc = update_progress_string(ctx, &progress_str);
if ( rc )
goto out;
}
stream_type == XC_MIG_STREAM_REMUS ||
stream_type == XC_MIG_STREAM_COLO);
- /*
- * TODO: Find some time to better tweak the live migration algorithm.
- *
- * These parameters are better than the legacy algorithm especially for
- * busy guests.
- */
- ctx.save.max_iterations = 5;
- ctx.save.dirty_threshold = 50;
-
/* Sanity checks for callbacks. */
if ( hvm )
assert(callbacks->switch_qemu_logdirty);