mirror of
https://github.com/checkpoint-restore/criu.git
synced 2026-07-17 16:47:50 +00:00
crtools/rpc/libcriu: Expose page compression
The compression paths have no supported user or RPC controls, and clients cannot discover whether CRIU was built with LZ4. Expose per-page and region compression, LZ4 acceleration, and worker concurrency for LZ4 decoding and eligible large zero fills through the CLI, configuration files, RPC, and libcriu. Add compression to the feature-check API and document each interface. Define --decompress-threads=0 as automatic worker concurrency and one as the serial default with zero-fill workers disabled. Values above one bound aggregate worker concurrency. Automatic mode derives a useful width from CPU affinity and the work in each batch. Reduce explicit requests above the available CPU count with a warning. Document the independent two-slot encoded-input budget. A local restore overlaps its next read only when it can reserve the second slot without waiting; otherwise it keeps synchronous I/O. Use the compression mode as the source of truth and validate every related value. An explicit RPC OFF clears ambient compression settings and rejects conflicting fields. Reject combinations unsupported by page server, stream, TLS, and image-format paths, and cover option and RPC precedence in the tests. Assisted-by: Codex:GPT-5 Assisted-by: Claude:claude-fable-5 Signed-off-by: Radostin Stoyanov <rstoyanov@fedoraproject.org>
This commit is contained in:
parent
85ac63b772
commit
3441f66b24
15 changed files with 612 additions and 4 deletions
|
|
@ -378,6 +378,60 @@ mount -t cgroup -o devices,freezer none devices,freezer
|
|||
Allows to link unlinked files back, if possible (modifies filesystem
|
||||
during *restore*).
|
||||
|
||||
*-c, --compress*::
|
||||
Enable LZ4 per-page compression of memory pages during *dump*.
|
||||
Each system page is compressed as an independent LZ4 block.
|
||||
Zero-filled pages are detected and stored with no data; pages that
|
||||
do not compress well are stored raw to avoid decompression overhead
|
||||
on *restore*. The compression choice made at *dump* time is recorded
|
||||
in the inventory image and applied automatically on *restore*.
|
||||
Compatible with *--page-server* when *--tls* is not used, *--stream*,
|
||||
*--lazy-pages*, iterative checkpointing, *--auto-dedup*, and the *dedup*
|
||||
command.
|
||||
|
||||
*--compress-region* 'size'::
|
||||
Enable LZ4 region compression of memory pages during *dump*.
|
||||
A run of consecutive pages totalling 'size' bytes is compressed as
|
||||
a single LZ4 block, which yields a better ratio and faster dump
|
||||
on heap-shaped data than per-page compression at the cost of
|
||||
slightly slower restore on partial reads. 'size' accepts K/M/G
|
||||
suffixes (e.g. *256K*, *1M*); it must be a multiple of the system
|
||||
page size and at most 4M. Mutually exclusive with *--compress*.
|
||||
Currently supports only the local image path (no
|
||||
*--page-server* / *--stream*). Hole-punching deduplication is
|
||||
skipped for compressed images since compressed pages are
|
||||
variably-sized.
|
||||
|
||||
*--compress-acceleration* 'N'::
|
||||
Set the LZ4 acceleration level for page compression (1 to 65537).
|
||||
Controls how thoroughly the compressor searches for matching byte
|
||||
sequences. The default value of 1 gives the best compression
|
||||
ratio. Higher values skip more match candidates, resulting in
|
||||
faster compression but larger output. Decompression speed is not
|
||||
affected. Implies *--compress* unless *--compress-region* is set.
|
||||
|
||||
*--decompress-threads* 'N'::
|
||||
Set worker concurrency for LZ4 decoding and eligible large zero fills,
|
||||
including the calling restore thread. The default value of 1 keeps each
|
||||
LZ4 decode serial and handles zero blocks without a worker pool;
|
||||
independent private, shmem, and memfd requests can still run concurrently.
|
||||
A value of 0 lets CRIU choose the concurrency for each batch. CRIU starts
|
||||
with the CPUs allowed by its affinity mask, then limits the width by the
|
||||
number and decoded size of the blocks in that batch and by the restore-wide
|
||||
CPU budget. Small batches and zero runs with fewer than two blocks remain
|
||||
serial. Values above 1 set an upper bound on aggregate worker concurrency
|
||||
instead of using the automatic CPU count. Explicit requests above the
|
||||
available CPU count are reduced with a warning. The accepted range is 0
|
||||
through 1024; this validation ceiling does not determine the automatic
|
||||
concurrency.
|
||||
Encoded staging memory is bounded independently of the thread count.
|
||||
Restore keeps at most two active 32 MiB encoded input buffers. When a
|
||||
local reader can reserve a second buffer without waiting, its calling thread
|
||||
reads the next payload while pool workers decode the current one. If
|
||||
another restore request already owns the second buffer, reading remains
|
||||
synchronous. The option can also be set through a configuration file
|
||||
(*decompress-threads 0*).
|
||||
|
||||
*--timeout* 'number'::
|
||||
Set a time limit in seconds for collecting tasks during the
|
||||
dump operation. The timeout is 10 seconds by default.
|
||||
|
|
|
|||
1
Makefile
1
Makefile
|
|
@ -479,6 +479,7 @@ shellcheck:
|
|||
shellcheck -x test/others/config-file/*.sh
|
||||
shellcheck -x test/others/action-script/*.sh
|
||||
shellcheck -x contrib/criu-service-client/test/*.sh
|
||||
shellcheck -x test/others/compression/*/*.sh
|
||||
|
||||
codespell:
|
||||
codespell
|
||||
|
|
|
|||
166
criu/config.c
166
criu/config.c
|
|
@ -1,6 +1,8 @@
|
|||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -13,6 +15,7 @@
|
|||
#include "cgroup.h"
|
||||
#include "cgroup-props.h"
|
||||
#include "common/bug.h"
|
||||
#include "compression.h"
|
||||
#include "cpu.h"
|
||||
#include "crtools.h"
|
||||
#include "cr_options.h"
|
||||
|
|
@ -551,6 +554,41 @@ static size_t parse_size(char *optarg)
|
|||
return (size_t)atol(optarg);
|
||||
}
|
||||
|
||||
static int parse_size_strict(const char *value, size_t *size)
|
||||
{
|
||||
unsigned long long number, multiplier = 1;
|
||||
char *end;
|
||||
|
||||
if (!value[0] || value[0] < '0' || value[0] > '9')
|
||||
return -1;
|
||||
|
||||
errno = 0;
|
||||
number = strtoull(value, &end, 10);
|
||||
if (errno == ERANGE || end == value)
|
||||
return -1;
|
||||
|
||||
if (*end) {
|
||||
switch (*end++) {
|
||||
case 'K':
|
||||
multiplier = 1024;
|
||||
break;
|
||||
case 'M':
|
||||
multiplier = 1024 * 1024;
|
||||
break;
|
||||
case 'G':
|
||||
multiplier = 1024ULL * 1024 * 1024;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (*end || number > SIZE_MAX / multiplier)
|
||||
return -1;
|
||||
|
||||
*size = (size_t)(number * multiplier);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int parse_join_ns(const char *ptr)
|
||||
{
|
||||
char *aux, *ns_file, *extra_opts = NULL;
|
||||
|
|
@ -634,7 +672,7 @@ int parse_options(int argc, char **argv, bool *usage_error, bool *has_exec_cmd,
|
|||
"no-" OPT_NAME, no_argument, SAVE_TO, false \
|
||||
}
|
||||
|
||||
static const char short_opts[] = "dSsRt:hD:o:v::x::Vr:jJ:lW:L:M:";
|
||||
static const char short_opts[] = "dSscRt:hD:o:v::x::Vr:jJ:lW:L:M:";
|
||||
static struct option long_opts[] = {
|
||||
{ "tree", required_argument, 0, 't' },
|
||||
{ "leave-stopped", no_argument, 0, 's' },
|
||||
|
|
@ -722,6 +760,10 @@ int parse_options(int argc, char **argv, bool *usage_error, bool *has_exec_cmd,
|
|||
BOOL_OPT("unprivileged", &opts.unprivileged),
|
||||
BOOL_OPT("ghost-fiemap", &opts.ghost_fiemap),
|
||||
BOOL_OPT(OPT_ALLOW_UPROBES, &opts.allow_uprobes),
|
||||
{ "compress", no_argument, 0, 'c' },
|
||||
{ "compress-acceleration", required_argument, 0, 1102 },
|
||||
{ "compress-region", required_argument, 0, 1103 },
|
||||
{ "decompress-threads", required_argument, 0, 1104 },
|
||||
{},
|
||||
};
|
||||
|
||||
|
|
@ -831,6 +873,58 @@ int parse_options(int argc, char **argv, bool *usage_error, bool *has_exec_cmd,
|
|||
} else
|
||||
opts.log_level++;
|
||||
break;
|
||||
case 'c':
|
||||
if (opts.compress_mode == COMPRESS_REGION) {
|
||||
pr_err("--compress conflicts with --compress-region\n");
|
||||
return 1;
|
||||
}
|
||||
opts.compress_mode = COMPRESS_PER_PAGE;
|
||||
break;
|
||||
case 1102: {
|
||||
char *endptr;
|
||||
long accel = strtol(optarg, &endptr, 10);
|
||||
|
||||
if (*endptr != '\0' || accel < 1 || accel > LZ4_MAX_ACCELERATION) {
|
||||
pr_err("Invalid --compress-acceleration value '%s' (must be 1..%d)\n",
|
||||
optarg, LZ4_MAX_ACCELERATION);
|
||||
return 1;
|
||||
}
|
||||
opts.compress_acceleration = accel;
|
||||
break;
|
||||
}
|
||||
case 1103: {
|
||||
size_t sz;
|
||||
|
||||
if (parse_size_strict(optarg, &sz) || sz == 0 ||
|
||||
sz % PAGE_SIZE != 0 || sz > MAX_REGION_SIZE) {
|
||||
pr_err("Invalid --compress-region '%s' (must be a multiple of %lu, max %lu)\n",
|
||||
optarg, (unsigned long)PAGE_SIZE,
|
||||
MAX_REGION_SIZE);
|
||||
return 1;
|
||||
}
|
||||
if (opts.compress_mode == COMPRESS_PER_PAGE) {
|
||||
pr_err("--compress-region conflicts with --compress\n");
|
||||
return 1;
|
||||
}
|
||||
opts.compress_region_size = sz;
|
||||
opts.compress_mode = COMPRESS_REGION;
|
||||
break;
|
||||
}
|
||||
case 1104: {
|
||||
char *endptr;
|
||||
long n;
|
||||
|
||||
errno = 0;
|
||||
n = strtol(optarg, &endptr, 10);
|
||||
if (errno == ERANGE || endptr == optarg || *endptr != '\0' ||
|
||||
n < 0 || n > 1024) {
|
||||
pr_err("Invalid --decompress-threads value '%s' (must be 0..1024)\n",
|
||||
optarg);
|
||||
return 1;
|
||||
}
|
||||
opts.decompress_threads = (unsigned int)n;
|
||||
break;
|
||||
}
|
||||
case 1043: {
|
||||
int fd;
|
||||
|
||||
|
|
@ -1097,6 +1191,76 @@ bad_arg:
|
|||
|
||||
int check_options(void)
|
||||
{
|
||||
/*
|
||||
* --compress-acceleration (CLI) or compress_acceleration (RPC) on
|
||||
* their own imply per-page compression. Resolve that here rather
|
||||
* than while parsing each option, so that passing acceleration
|
||||
* before --compress-region is not mistaken for a conflict with an
|
||||
* implicit --compress.
|
||||
*/
|
||||
if (opts.compress_acceleration && opts.compress_mode == COMPRESS_OFF)
|
||||
opts.compress_mode = COMPRESS_PER_PAGE;
|
||||
|
||||
/*
|
||||
* Compression is selected by the dump client and encoded in each page
|
||||
* server wire command. Letting the server select a mode independently
|
||||
* can make the payload disagree with the inventory written by the client.
|
||||
*/
|
||||
if (opts.mode == CR_PAGE_SERVER && opts.compress_mode != COMPRESS_OFF) {
|
||||
pr_err("Memory page compression options apply to the dump client, not the page server\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (opts.compress_mode) {
|
||||
#ifndef CONFIG_LZ4
|
||||
pr_err("Memory page compression requires CRIU built with LZ4 support (CONFIG_LZ4)\n");
|
||||
return 1;
|
||||
#else
|
||||
if (opts.compress_mode == COMPRESS_REGION) {
|
||||
if (opts.compress_region_size == 0)
|
||||
opts.compress_region_size = DEFAULT_REGION_SIZE;
|
||||
if (opts.compress_region_size % PAGE_SIZE != 0 ||
|
||||
opts.compress_region_size > MAX_REGION_SIZE) {
|
||||
pr_err("Invalid compress region size %u\n",
|
||||
opts.compress_region_size);
|
||||
return 1;
|
||||
}
|
||||
pr_debug("Region compression of memory pages is enabled (region=%u bytes)\n",
|
||||
opts.compress_region_size);
|
||||
} else {
|
||||
pr_debug("Per-page compression of memory pages is enabled\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Region compression is currently only implemented for the local
|
||||
* dump and restore paths. The page-server
|
||||
* and image-streamer wire formats are per-page; combining them with
|
||||
* --compress-region would produce an image the receiver cannot read.
|
||||
* Reject the combination early.
|
||||
*/
|
||||
if (opts.compress_mode == COMPRESS_REGION) {
|
||||
if (opts.use_page_server || opts.addr) {
|
||||
pr_err("--compress-region is not supported with --page-server\n");
|
||||
return 1;
|
||||
}
|
||||
if (opts.stream) {
|
||||
pr_err("--compress-region is not supported with --stream\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The compressed page-server sender writes its records straight to
|
||||
* the socket and does not route them through the TLS helpers, so a
|
||||
* TLS page-server would receive plaintext into the encrypted stream.
|
||||
* Reject the combination until compressed sends learn to use TLS.
|
||||
*/
|
||||
if (opts.compress_mode && opts.tls && (opts.use_page_server || opts.addr)) {
|
||||
pr_err("Memory page compression is not supported with a TLS page-server\n");
|
||||
return 1;
|
||||
}
|
||||
if (opts.tcp_established_ok)
|
||||
pr_info("Will dump/restore TCP connections\n");
|
||||
if (opts.tcp_skip_in_flight)
|
||||
|
|
|
|||
|
|
@ -1809,6 +1809,16 @@ static int check_binfmt_misc_sandboxing(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int check_compress(void)
|
||||
{
|
||||
#ifdef CONFIG_LZ4
|
||||
return 0;
|
||||
#else
|
||||
pr_info("LZ4 compression support is not compiled in\n");
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
struct feature_list {
|
||||
char *name;
|
||||
int (*func)(void);
|
||||
|
|
@ -1860,6 +1870,7 @@ static struct feature_list feature_list[] = {
|
|||
{ "overlayfs_maps", check_overlayfs_maps },
|
||||
{ "pagemap_scan_guard_pages", check_pagemap_scan_guard_pages },
|
||||
{ "binfmt_misc_sandboxing", check_binfmt_misc_sandboxing },
|
||||
{ "compress", check_compress },
|
||||
{ NULL, NULL },
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@
|
|||
|
||||
#include "cr-errno.h"
|
||||
#include "namespaces.h"
|
||||
#include "compression.h"
|
||||
|
||||
unsigned int service_sk_ino = -1;
|
||||
|
||||
|
|
@ -837,6 +838,64 @@ static int setup_opts_from_req(int sk, CriuOpts *req)
|
|||
if (req->mntns_compat_mode)
|
||||
opts.mntns_compat_mode = true;
|
||||
|
||||
if (req->has_compress) {
|
||||
if (req->compress > COMPRESS_REGION) {
|
||||
pr_err("Invalid compress value %u\n", req->compress);
|
||||
goto err;
|
||||
}
|
||||
opts.compress_mode = req->compress;
|
||||
if (req->compress == COMPRESS_OFF) {
|
||||
/*
|
||||
* An explicit RPC setting has precedence over values loaded
|
||||
* from the service configuration. libcriu clears the related
|
||||
* request fields when compression is disabled; reject a raw
|
||||
* RPC request which asks for both settings at once.
|
||||
*/
|
||||
if (req->has_compress_acceleration ||
|
||||
req->has_compress_region_size) {
|
||||
pr_err("compress=off conflicts with compression tuning options\n");
|
||||
goto err;
|
||||
}
|
||||
opts.compress_acceleration = 0;
|
||||
opts.compress_region_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (req->has_compress_acceleration) {
|
||||
if (req->compress_acceleration < 1 ||
|
||||
req->compress_acceleration > LZ4_MAX_ACCELERATION) {
|
||||
pr_err("Invalid compress_acceleration value %u (must be 1..%d)\n",
|
||||
req->compress_acceleration, LZ4_MAX_ACCELERATION);
|
||||
goto err;
|
||||
}
|
||||
opts.compress_acceleration = req->compress_acceleration;
|
||||
}
|
||||
|
||||
if (req->has_compress_region_size) {
|
||||
if (req->compress_region_size == 0 ||
|
||||
req->compress_region_size % PAGE_SIZE != 0 ||
|
||||
req->compress_region_size > MAX_REGION_SIZE) {
|
||||
pr_err("Invalid compress_region_size value %u\n",
|
||||
req->compress_region_size);
|
||||
goto err;
|
||||
}
|
||||
if (opts.compress_mode == COMPRESS_PER_PAGE) {
|
||||
pr_err("compress_region_size conflicts with compress=per-page\n");
|
||||
goto err;
|
||||
}
|
||||
opts.compress_region_size = req->compress_region_size;
|
||||
opts.compress_mode = COMPRESS_REGION;
|
||||
}
|
||||
|
||||
if (req->has_decompress_threads) {
|
||||
if (req->decompress_threads > 1024) {
|
||||
pr_err("Invalid decompress_threads value %u (must be 0..1024)\n",
|
||||
req->decompress_threads);
|
||||
goto err;
|
||||
}
|
||||
opts.decompress_threads = req->decompress_threads;
|
||||
}
|
||||
|
||||
if (check_options())
|
||||
goto err;
|
||||
|
||||
|
|
@ -1227,6 +1286,8 @@ static int handle_feature_check(int sk, CriuReq *msg)
|
|||
feat.lazy_pages = false;
|
||||
feat.has_pidfd_store = 1;
|
||||
feat.pidfd_store = false;
|
||||
feat.has_mem_compression = 1;
|
||||
feat.mem_compression = false;
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
|
|
@ -1249,6 +1310,13 @@ static int handle_feature_check(int sk, CriuReq *msg)
|
|||
if ((msg->features->has_pidfd_store == 1) && (msg->features->pidfd_store == true))
|
||||
feat.pidfd_store = kdat.has_pidfd_getfd && kdat.has_pidfd_open;
|
||||
|
||||
if ((msg->features->has_mem_compression == 1) &&
|
||||
(msg->features->mem_compression == true)) {
|
||||
#ifdef CONFIG_LZ4
|
||||
feat.mem_compression = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
resp.features = &feat;
|
||||
resp.type = msg->type;
|
||||
/* The feature check is working, actual results are in resp.features */
|
||||
|
|
|
|||
|
|
@ -556,6 +556,23 @@ usage:
|
|||
" will be punched from the image\n"
|
||||
" --pre-dump-mode splice - parasite based pre-dumping (default)\n"
|
||||
" read - process_vm_readv syscall based pre-dumping\n"
|
||||
#ifdef CONFIG_LZ4
|
||||
" -c|--compress enable LZ4 per-page compression of memory pages\n"
|
||||
" --compress-region size\n"
|
||||
" enable memory page compression for given region size;\n"
|
||||
" size accepts K/M/G suffixes (e.g. 256K, 1M);\n"
|
||||
" valid range: page-size multiples up to 4M\n"
|
||||
" --compress-acceleration N\n"
|
||||
" LZ4 acceleration (default is 1; max is 65537).\n"
|
||||
" Higher values favor speed over compression ratio.\n"
|
||||
" Implies --compress unless --compress-region is set.\n"
|
||||
" --decompress-threads N\n"
|
||||
" worker concurrency for LZ4 decode and zero fill\n"
|
||||
" (default: 1 = serial per request; 0 = auto;\n"
|
||||
" N > 1 = aggregate worker limit).\n"
|
||||
" Valid range: 0..1024; affinity, batch work, and the\n"
|
||||
" restore-wide budget may reduce active width.\n"
|
||||
#endif
|
||||
"\n"
|
||||
"Page/Service server options:\n"
|
||||
" --address ADDR address of server or service\n"
|
||||
|
|
|
|||
|
|
@ -151,6 +151,22 @@ message criu_opts {
|
|||
optional bool display_stats = 70;
|
||||
optional bool log_to_stderr = 71;
|
||||
optional criu_image_io_mode image_io_mode = 72 [default = IMAGE_IO_WRITEBACK];
|
||||
/*
|
||||
* Memory page compression mode (enum compress_mode):
|
||||
* 0 = off, 1 = per-page, 2 = region. Default 0.
|
||||
*/
|
||||
optional uint32 compress = 73;
|
||||
optional uint32 compress_acceleration = 74;
|
||||
optional uint32 compress_region_size = 75;
|
||||
/*
|
||||
* Worker concurrency for LZ4 decoding and eligible large zero fills.
|
||||
* 0 selects automatic concurrency; 1, the default, keeps LZ4 decoding
|
||||
* serial and disables zero-fill workers; values above 1 limit aggregate
|
||||
* worker concurrency. Active concurrency also depends on available CPUs,
|
||||
* useful batch work, and the restore-wide CPU budget. Valid range: 0
|
||||
* through 1024. Restore-time only.
|
||||
*/
|
||||
optional uint32 decompress_threads = 76 [default = 1];
|
||||
/* optional bool check_mounts = 128; */
|
||||
}
|
||||
|
||||
|
|
@ -198,6 +214,8 @@ message criu_features {
|
|||
optional bool mem_track = 1;
|
||||
optional bool lazy_pages = 2;
|
||||
optional bool pidfd_store = 3;
|
||||
/* Memory page compression support is available in this CRIU build. */
|
||||
optional bool mem_compression = 4;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
86
lib/c/criu.c
86
lib/c/criu.c
|
|
@ -376,6 +376,85 @@ void criu_set_track_mem(bool track_mem)
|
|||
criu_local_set_track_mem(global_opts, track_mem);
|
||||
}
|
||||
|
||||
int criu_local_set_compress(criu_opts *opts, enum criu_compress_mode mode)
|
||||
{
|
||||
if (mode < CRIU_COMPRESS_OFF || mode > CRIU_COMPRESS_REGION)
|
||||
return -EINVAL;
|
||||
|
||||
opts->rpc->has_compress = true;
|
||||
opts->rpc->compress = mode;
|
||||
if (mode != CRIU_COMPRESS_REGION) {
|
||||
opts->rpc->has_compress_region_size = false;
|
||||
opts->rpc->compress_region_size = 0;
|
||||
}
|
||||
if (mode == CRIU_COMPRESS_OFF) {
|
||||
opts->rpc->has_compress_acceleration = false;
|
||||
opts->rpc->compress_acceleration = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int criu_set_compress(enum criu_compress_mode mode)
|
||||
{
|
||||
return criu_local_set_compress(global_opts, mode);
|
||||
}
|
||||
|
||||
int criu_local_set_compress_acceleration(criu_opts *opts,
|
||||
unsigned int acceleration)
|
||||
{
|
||||
if (acceleration < 1 || acceleration > CRIU_COMPRESS_MAX_ACCELERATION)
|
||||
return -EINVAL;
|
||||
|
||||
opts->rpc->has_compress_acceleration = true;
|
||||
opts->rpc->compress_acceleration = acceleration;
|
||||
if (!opts->rpc->has_compress ||
|
||||
opts->rpc->compress == CRIU_COMPRESS_OFF) {
|
||||
opts->rpc->has_compress = true;
|
||||
opts->rpc->compress = CRIU_COMPRESS_PER_PAGE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int criu_set_compress_acceleration(unsigned int acceleration)
|
||||
{
|
||||
return criu_local_set_compress_acceleration(global_opts, acceleration);
|
||||
}
|
||||
|
||||
int criu_local_set_compress_region_size(criu_opts *opts, unsigned int bytes)
|
||||
{
|
||||
long page_size = sysconf(_SC_PAGESIZE);
|
||||
|
||||
if (page_size <= 0 || !bytes || bytes > CRIU_COMPRESS_MAX_REGION_SIZE ||
|
||||
bytes % (unsigned long)page_size)
|
||||
return -EINVAL;
|
||||
|
||||
opts->rpc->has_compress_region_size = true;
|
||||
opts->rpc->compress_region_size = bytes;
|
||||
opts->rpc->has_compress = true;
|
||||
opts->rpc->compress = CRIU_COMPRESS_REGION;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int criu_set_compress_region_size(unsigned int bytes)
|
||||
{
|
||||
return criu_local_set_compress_region_size(global_opts, bytes);
|
||||
}
|
||||
|
||||
int criu_local_set_decompress_threads(criu_opts *opts, unsigned int threads)
|
||||
{
|
||||
if (threads > CRIU_DECOMPRESS_MAX_THREADS)
|
||||
return -EINVAL;
|
||||
|
||||
opts->rpc->has_decompress_threads = true;
|
||||
opts->rpc->decompress_threads = threads;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int criu_set_decompress_threads(unsigned int threads)
|
||||
{
|
||||
return criu_local_set_decompress_threads(global_opts, threads);
|
||||
}
|
||||
|
||||
void criu_local_set_auto_dedup(criu_opts *opts, bool auto_dedup)
|
||||
{
|
||||
opts->rpc->has_auto_dedup = true;
|
||||
|
|
@ -2007,6 +2086,10 @@ int criu_local_feature_check(criu_opts *opts, struct criu_feature_check *feature
|
|||
criu_features.has_pidfd_store = true;
|
||||
criu_features.pidfd_store = true;
|
||||
}
|
||||
if (features_copy.mem_compression) {
|
||||
criu_features.has_mem_compression = true;
|
||||
criu_features.mem_compression = true;
|
||||
}
|
||||
req.features = &criu_features;
|
||||
|
||||
ret = send_req_and_recv_resp(opts, &req, &resp);
|
||||
|
|
@ -2025,6 +2108,9 @@ int criu_local_feature_check(criu_opts *opts, struct criu_feature_check *feature
|
|||
if (resp->features->has_pidfd_store) {
|
||||
features_copy.pidfd_store = resp->features->pidfd_store;
|
||||
}
|
||||
if (resp->features->has_mem_compression) {
|
||||
features_copy.mem_compression = resp->features->mem_compression;
|
||||
}
|
||||
memcpy(features, &features_copy, size);
|
||||
} else {
|
||||
ret = -EBADE;
|
||||
|
|
|
|||
28
lib/c/criu.h
28
lib/c/criu.h
|
|
@ -60,6 +60,17 @@ enum criu_image_io_mode {
|
|||
CRIU_IMAGE_IO_DIRECT = 1,
|
||||
};
|
||||
|
||||
enum criu_compress_mode {
|
||||
CRIU_COMPRESS_OFF = 0,
|
||||
CRIU_COMPRESS_PER_PAGE = 1,
|
||||
CRIU_COMPRESS_REGION = 2,
|
||||
};
|
||||
|
||||
#define CRIU_COMPRESS_MAX_ACCELERATION 65537U
|
||||
#define CRIU_COMPRESS_MAX_REGION_SIZE (4U * 1024U * 1024U)
|
||||
/* Maximum explicit setting; automatic concurrency is not capped by it. */
|
||||
#define CRIU_DECOMPRESS_MAX_THREADS 1024U
|
||||
|
||||
int criu_set_service_address(const char *path);
|
||||
void criu_set_service_fd(int fd);
|
||||
int criu_set_service_binary(const char *path);
|
||||
|
|
@ -90,6 +101,17 @@ void criu_set_unprivileged(bool unprivileged);
|
|||
void criu_set_orphan_pts_master(bool orphan_pts_master);
|
||||
void criu_set_file_locks(bool file_locks);
|
||||
void criu_set_track_mem(bool track_mem);
|
||||
int criu_set_compress(enum criu_compress_mode mode);
|
||||
int criu_set_compress_acceleration(unsigned int acceleration);
|
||||
int criu_set_compress_region_size(unsigned int bytes);
|
||||
/*
|
||||
* Worker concurrency for LZ4 decoding and eligible large zero fills:
|
||||
* 0 = auto, 1 = serial/no zero-fill workers (default), N > 1 = aggregate
|
||||
* worker limit.
|
||||
* CPU affinity, useful batch work, and the shared budget may reduce active
|
||||
* concurrency.
|
||||
*/
|
||||
int criu_set_decompress_threads(unsigned int threads);
|
||||
void criu_set_auto_dedup(bool auto_dedup);
|
||||
void criu_set_force_irmap(bool force_irmap);
|
||||
void criu_set_link_remap(bool link_remap);
|
||||
|
|
@ -253,6 +275,11 @@ void criu_local_set_skip_file_rwx_check(criu_opts *opts, bool skip_file_rwx_chec
|
|||
void criu_local_set_orphan_pts_master(criu_opts *opts, bool orphan_pts_master);
|
||||
void criu_local_set_file_locks(criu_opts *opts, bool file_locks);
|
||||
void criu_local_set_track_mem(criu_opts *opts, bool track_mem);
|
||||
int criu_local_set_compress(criu_opts *opts, enum criu_compress_mode mode);
|
||||
int criu_local_set_compress_acceleration(criu_opts *opts, unsigned int acceleration);
|
||||
int criu_local_set_compress_region_size(criu_opts *opts, unsigned int bytes);
|
||||
/* Uses the same worker-concurrency values as criu_set_decompress_threads(). */
|
||||
int criu_local_set_decompress_threads(criu_opts *opts, unsigned int threads);
|
||||
void criu_local_set_auto_dedup(criu_opts *opts, bool auto_dedup);
|
||||
void criu_local_set_force_irmap(criu_opts *opts, bool force_irmap);
|
||||
void criu_local_set_link_remap(criu_opts *opts, bool link_remap);
|
||||
|
|
@ -327,6 +354,7 @@ struct criu_feature_check {
|
|||
bool mem_track;
|
||||
bool lazy_pages;
|
||||
bool pidfd_store;
|
||||
bool mem_compression;
|
||||
};
|
||||
|
||||
int criu_feature_check(struct criu_feature_check *features, size_t size);
|
||||
|
|
|
|||
|
|
@ -386,6 +386,7 @@ run_non_shardable_tests() {
|
|||
|
||||
# config file parser and parameter testing
|
||||
make -C test/others/config-file run
|
||||
make -C test/others/compression/page-server run
|
||||
|
||||
# action script testing
|
||||
make -C test/others/action-script run
|
||||
|
|
|
|||
3
test/others/compression/page-server/Makefile
Normal file
3
test/others/compression/page-server/Makefile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
run:
|
||||
./run.sh
|
||||
.PHONY: run
|
||||
118
test/others/compression/page-server/run.sh
Executable file
118
test/others/compression/page-server/run.sh
Executable file
|
|
@ -0,0 +1,118 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Compression is a dump-client setting carried by page-server wire commands.
|
||||
# A page server must reject local compression settings instead of silently
|
||||
# changing how ordinary PS_IOV_ADD_F payloads are stored.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck source=test/others/env.sh
|
||||
source ../../env.sh
|
||||
|
||||
LOG="page-server-options.$$.log"
|
||||
|
||||
function cleanup {
|
||||
rm -f "$LOG"
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
function fail {
|
||||
echo "FAIL: $*"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function check_rejected {
|
||||
local name="$1"
|
||||
shift
|
||||
local status
|
||||
|
||||
rm -f "$LOG"
|
||||
set +e
|
||||
timeout --signal=KILL 5 "${CRIU}" page-server --no-default-config \
|
||||
-D . -o "$LOG" --port 54321 "$@"
|
||||
status=$?
|
||||
set -e
|
||||
|
||||
if [ "$status" -ne 1 ]; then
|
||||
fail "$name returned $status instead of rejecting the option"
|
||||
fi
|
||||
grep -q "compression options apply to the dump client" "$LOG" || \
|
||||
fail "$name did not report the page-server compression error"
|
||||
}
|
||||
|
||||
function check_invalid_region {
|
||||
local value="$1"
|
||||
local status
|
||||
|
||||
rm -f "$LOG"
|
||||
set +e
|
||||
timeout --foreground --kill-after=1s 5s \
|
||||
"${CRIU}" page-server --no-default-config -D . \
|
||||
--port 54321 --compress-region="$value" > "$LOG" 2>&1
|
||||
status=$?
|
||||
set -e
|
||||
|
||||
if [ "$status" -ne 1 ]; then
|
||||
fail "malformed region '$value' returned $status"
|
||||
fi
|
||||
grep -q "Invalid --compress-region" "$LOG" || \
|
||||
fail "malformed region '$value' was not rejected by its parser"
|
||||
}
|
||||
|
||||
function check_invalid_threads {
|
||||
local value="$1"
|
||||
local status
|
||||
|
||||
rm -f "$LOG"
|
||||
set +e
|
||||
timeout --foreground --kill-after=1s 5s \
|
||||
"${CRIU}" page-server --no-default-config -D . --port 54321 \
|
||||
--decompress-threads="$value" > "$LOG" 2>&1
|
||||
status=$?
|
||||
set -e
|
||||
|
||||
if [ "$status" -ne 1 ]; then
|
||||
fail "malformed decompression thread count '$value' returned $status"
|
||||
fi
|
||||
grep -q "Invalid --decompress-threads" "$LOG" || \
|
||||
fail "malformed decompression thread count '$value' was not rejected"
|
||||
}
|
||||
|
||||
function check_valid_threads {
|
||||
local value="$1"
|
||||
local port="$2"
|
||||
local status
|
||||
|
||||
rm -f "$LOG"
|
||||
set +e
|
||||
timeout --foreground --kill-after=1s 2s \
|
||||
"${CRIU}" page-server --no-default-config -D . --port "$port" \
|
||||
--decompress-threads="$value" > "$LOG" 2>&1
|
||||
status=$?
|
||||
set -e
|
||||
|
||||
# The option parses; the server waits for connections until the
|
||||
# timeout ends it.
|
||||
if [ "$status" -ne 124 ] && [ "$status" -ne 137 ]; then
|
||||
fail "valid decompression thread count '$value' returned $status"
|
||||
fi
|
||||
if grep -q "Invalid --decompress-threads" "$LOG"; then
|
||||
fail "valid decompression thread count '$value' was rejected"
|
||||
fi
|
||||
}
|
||||
|
||||
check_rejected compress --compress
|
||||
check_rejected acceleration --compress-acceleration 2
|
||||
check_rejected region --compress-region 64K
|
||||
check_invalid_region 64Kjunk
|
||||
check_invalid_region 64MgarbageK
|
||||
check_invalid_region 64KB
|
||||
check_invalid_region -64K
|
||||
check_invalid_region 18446744073709551616K
|
||||
check_invalid_threads ""
|
||||
# Check both special values: 0 selects automatic concurrency and 1, the default, is serial per request.
|
||||
check_valid_threads 0 54322
|
||||
check_valid_threads 1 54323
|
||||
|
||||
echo "Test PASSED"
|
||||
|
|
@ -51,6 +51,9 @@ run_test() {
|
|||
fi
|
||||
}
|
||||
|
||||
if criu check --feature compress > /dev/null; then
|
||||
export CRIU_FEATURE_COMPRESS=1
|
||||
fi
|
||||
run_test test_sub
|
||||
run_test test_self
|
||||
run_test test_notify
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ int main(int argc, char **argv)
|
|||
bool mem_track = 0;
|
||||
bool lazy_pages = 0;
|
||||
bool pidfd_store = 0;
|
||||
bool mem_compression = 0;
|
||||
struct criu_feature_check features = {
|
||||
.mem_track = true,
|
||||
.lazy_pages = true,
|
||||
.pidfd_store = true,
|
||||
.mem_compression = true,
|
||||
};
|
||||
|
||||
printf("--- Start feature check ---\n");
|
||||
|
|
@ -39,6 +41,10 @@ int main(int argc, char **argv)
|
|||
if (env) {
|
||||
pidfd_store = true;
|
||||
}
|
||||
env = getenv("CRIU_FEATURE_COMPRESS");
|
||||
if (env) {
|
||||
mem_compression = true;
|
||||
}
|
||||
|
||||
ret = criu_feature_check(&features, sizeof(features) + 1);
|
||||
printf(" `- passing too large structure to libcriu should return -1: %d\n", ret);
|
||||
|
|
@ -60,6 +66,10 @@ int main(int argc, char **argv)
|
|||
printf(" `- pidfd_store: %d - expected : %d\n", features.pidfd_store, pidfd_store);
|
||||
if (features.pidfd_store != pidfd_store)
|
||||
return -1;
|
||||
printf(" `- mem_compression: %d - expected : %d\n",
|
||||
features.mem_compression, mem_compression);
|
||||
if (features.mem_compression != mem_compression)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,15 +67,18 @@ static int create_criu_config_file(void)
|
|||
}
|
||||
|
||||
fprintf(fp, "log-file=%s\n", log_file);
|
||||
fprintf(fp, "compress-acceleration=2\n");
|
||||
fflush(fp);
|
||||
fclose(fp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_log_file(void)
|
||||
static int check_log_file(const char *forbidden)
|
||||
{
|
||||
struct stat st;
|
||||
char line[512];
|
||||
FILE *fp;
|
||||
|
||||
if (stat(log_file, &st) < 0) {
|
||||
perror("Config file does not exist");
|
||||
|
|
@ -87,6 +90,20 @@ static int check_log_file(void)
|
|||
return -1;
|
||||
}
|
||||
|
||||
fp = fopen(log_file, "r");
|
||||
if (!fp) {
|
||||
perror("Failed to open log file");
|
||||
return -1;
|
||||
}
|
||||
while (forbidden && fgets(line, sizeof(line), fp)) {
|
||||
if (strstr(line, forbidden)) {
|
||||
fprintf(stderr, "Unexpected log message: %s", line);
|
||||
fclose(fp);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
unlink(log_file);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -169,6 +186,10 @@ int main(int argc, char **argv)
|
|||
printf("Setting dump RPC config file: %s\n", conf_file);
|
||||
criu_set_config_file(conf_file);
|
||||
criu_set_log_file("dump.log");
|
||||
if (criu_set_compress(CRIU_COMPRESS_OFF)) {
|
||||
fprintf(stderr, "Failed to disable compression\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = criu_dump();
|
||||
if (ret < 0) {
|
||||
|
|
@ -181,13 +202,14 @@ int main(int argc, char **argv)
|
|||
printf(" `- Dump succeeded\n");
|
||||
waitpid(pid, NULL, 0);
|
||||
|
||||
if (check_log_file()) {
|
||||
if (check_log_file("compression of memory pages is enabled")) {
|
||||
printf("Error: log file not overwritten by RPC config file\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
printf("--- Restore loop ---\n");
|
||||
criu_init_opts();
|
||||
criu_set_service_binary(argv[1]);
|
||||
criu_set_images_dir_fd(img_fd);
|
||||
criu_set_log_level(CRIU_LOG_DEBUG);
|
||||
|
||||
|
|
@ -195,6 +217,10 @@ int main(int argc, char **argv)
|
|||
printf("Setting restore RPC config file: %s\n", conf_file);
|
||||
criu_set_config_file(conf_file);
|
||||
criu_set_log_file("restore.log");
|
||||
if (criu_set_compress(CRIU_COMPRESS_OFF)) {
|
||||
fprintf(stderr, "Failed to disable compression\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
pid = criu_restore_child();
|
||||
if (pid <= 0) {
|
||||
|
|
@ -206,7 +232,7 @@ int main(int argc, char **argv)
|
|||
printf(" `- Restore returned pid %d\n", pid);
|
||||
kill(pid, SIGUSR1);
|
||||
|
||||
if (check_log_file()) {
|
||||
if (check_log_file("compression of memory pages is enabled")) {
|
||||
printf("Error: log file not overwritten by RPC config file\n");
|
||||
goto cleanup;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue