[CRIU] [PATCH] test: bers -- Initial commit

Cyrill Gorcunov gorcunov at openvz.org
Tue Feb 18 03:04:47 PST 2014


bers stads for berserker which should eat computer
resources emulating "load" for CRIU performance testing.

It's still far from being complete but can do trivial things:

 - generate mmap's with memory dirtified
 - open files

Nothing serious.

Signed-off-by: Cyrill Gorcunov <gorcunov at openvz.org>
---
 test/bers/Makefile |  47 +++++++
 test/bers/bers.c   | 399 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 test/bers/bers.txt |  74 ++++++++++
 3 files changed, 520 insertions(+)
 create mode 100644 test/bers/Makefile
 create mode 100644 test/bers/bers.c
 create mode 100644 test/bers/bers.txt

diff --git a/test/bers/Makefile b/test/bers/Makefile
new file mode 100644
index 000000000000..74b6142e7f04
--- /dev/null
+++ b/test/bers/Makefile
@@ -0,0 +1,47 @@
+ifeq ($(strip $(V)),)
+	E = @echo
+	Q = @
+else
+	E = @\#
+	Q =
+endif
+
+export E Q
+
+ASCIIDOC	:= asciidoc
+A2X		:= a2x
+XMLTO		:= xmlto
+
+SRC		+= bers.txt
+XMLS		:= $(patsubst %.txt,%.xml,$(SRC))
+MANS		:= $(patsubst %.txt,%.8,$(SRC))
+
+%.8: %.txt
+	$(E) "  GEN     " $@
+	$(Q) $(ASCIIDOC) -b docbook -d manpage -o $(patsubst %.8,%.xml,$@) $<
+	$(Q) $(XMLTO) man --skip-validation $(patsubst %.8,%.xml,$@) 2>/dev/null
+
+docs: $(MANS)
+	@true
+
+CFLAGS := -O0 -ggdb3
+LIBS := -lpthread
+
+%.o: %.c
+	$(E) "  CC      " $@
+	$(Q) $(CC) -c -o $@ $(CFLAGS) $^
+
+bers: bers.o
+	$(E) "  LINK    " $@
+	$(Q) $(CC) -o $@ $(CFLAGS) $(LIBS) $^
+
+all: bers
+	@true
+
+clean:
+	$(E) "  CLEAN   "
+	$(Q) rm -f $(XMLS) $(MANS)
+	$(Q) rm -f bers.o
+	$(Q) rm -f bers
+
+.PHONY: all docs clean
diff --git a/test/bers/bers.c b/test/bers/bers.c
new file mode 100644
index 000000000000..18ac9a5ac07b
--- /dev/null
+++ b/test/bers/bers.c
@@ -0,0 +1,399 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <string.h>
+#include <limits.h>
+#include <stdbool.h>
+
+#include <pthread.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <syscall.h>
+
+#define min(x, y) ({				\
+	typeof(x) _min1 = (x);			\
+	typeof(y) _min2 = (y);			\
+	(void) (&_min1 == &_min2);		\
+	_min1 < _min2 ? _min1 : _min2; })
+
+#define max(x, y) ({				\
+	typeof(x) _max1 = (x);			\
+	typeof(y) _max2 = (y);			\
+	(void) (&_max1 == &_max2);		\
+	_max1 > _max2 ? _max1 : _max2; })
+
+#define MAX_CHUNK		4096
+#define PAGE_SIZE		4096
+
+#define pr_info(fmt, ...)				\
+	printf("%8d: " fmt, sys_gettid(), ##__VA_ARGS__)
+
+#define pr_err(fmt, ...)				\
+	printf("%8d: Error (%s:%d): " fmt, sys_gettid(),\
+		       __FILE__, __LINE__, ##__VA_ARGS__)
+
+#define pr_perror(fmt, ...)				\
+	pr_err(fmt ": %m\n", ##__VA_ARGS__)
+
+#define pr_msg(fmt, ...)				\
+	printf(fmt, ##__VA_ARGS__)
+
+
+#define pr_trace(fmt, ...)				\
+	printf("%8d: %s: " fmt, sys_gettid(), __func__,	\
+		##__VA_ARGS__)
+
+enum {
+	MEM_FILL_MODE_NONE	= 0,
+	MEM_FILL_MODE_ALL	= 1,
+	MEM_FILL_MODE_LIGHT	= 2,
+	MEM_FILL_MODE_DIRTIFY	= 3,
+};
+
+typedef struct {
+	pthread_mutex_t		mutex;
+	pthread_mutexattr_t	mutex_attr;
+
+	size_t			opt_tasks;
+
+	size_t			opt_files;
+	size_t			opt_file_size;
+	int			prev_fd[MAX_CHUNK];
+
+	size_t			opt_mem;
+	size_t			opt_mem_chunks;
+	size_t			opt_mem_chunk_size;
+	int			opt_mem_fill_mode;
+	int			opt_mem_cycle_mode;
+	unsigned int		opt_refresh_time;
+
+	char			*opt_work_dir;
+	int			work_dir_fd;
+	DIR			*work_dir;
+
+	pid_t			err_pid;
+	int			err_no;
+
+	unsigned long		prev_map[MAX_CHUNK];
+} shared_data_t;
+
+static shared_data_t *shared;
+
+static int sys_gettid(void)
+{
+	return syscall(__NR_gettid);
+}
+
+static void dirtify_memory(unsigned long *chunks, size_t nr_chunks,
+			   size_t chunk_size, int mode, const size_t nr_pages)
+{
+	void *page;
+	size_t i;
+
+	pr_trace("filling memory\n");
+	switch (mode) {
+	case MEM_FILL_MODE_LIGHT:
+		*((unsigned long *)chunks[0]) = -1ul;
+		break;
+	case MEM_FILL_MODE_ALL:
+		for (i = 0; i < nr_chunks; i++)
+			memset((void *)chunks[i], (char)i, chunk_size);
+		break;
+	case MEM_FILL_MODE_DIRTIFY:
+		for (i = 0; i < nr_chunks; i++)
+			*((unsigned long *)chunks[i]) = -1ul;
+		break;
+	}
+}
+
+static void dirtify_files(int *fd, size_t nr_files, size_t size)
+{
+	size_t buf[8192];
+	size_t i, j, c;
+
+	/*
+	 * Note we don't write any _sane_ data here, the only
+	 * important thing is I/O activity by self.
+	 */
+
+	for (i = 0; i < nr_files; i++) {
+		size_t c = min(size, sizeof(buf));
+		size_t left = size;
+
+		while (left > 0) {
+			write(fd[i], buf, c);
+			left -= c;
+			c = min(left, sizeof(buf));
+		}
+	}
+}
+
+static int create_files(shared_data_t *shared, int *fd, size_t nr_files)
+{
+	char path[PATH_MAX];
+	size_t i;
+
+	memset(fd, 0xff, sizeof(fd));
+
+	pr_info("\tCreating %lu files\n", shared->opt_files);
+
+	for (i = 0; i < shared->opt_files; i++) {
+		if (shared->prev_fd[i] != -1) {
+			close(shared->prev_fd[i]);
+			shared->prev_fd[i] = -1;
+		}
+		snprintf(path, sizeof(path), "%08d-%04d-temp", sys_gettid(), i);
+		fd[i] = openat(shared->work_dir_fd, path, O_RDWR | O_CREAT | O_TRUNC, 0666);
+		if (fd[i] < 0) {
+			pr_perror("Can't open %s/%s", shared->opt_work_dir, path);
+			shared->err_pid = sys_gettid();
+			shared->err_no = -errno;
+			return -1;
+		}
+		shared->prev_fd[i] = fd[i];
+	}
+
+	return 0;
+}
+
+static void work_on_fork(shared_data_t *shared)
+{
+	const size_t nr_pages = shared->opt_mem_chunk_size / PAGE_SIZE;
+	unsigned long chunks[MAX_CHUNK] = { };
+	int fd[MAX_CHUNK];
+	size_t i;
+	void *mem;
+
+	pr_trace("locking\n");
+	pthread_mutex_lock(&shared->mutex);
+	pr_trace("init\n");
+
+	pr_info("\tCreating %lu mmaps each %lu K\n",
+		shared->opt_mem_chunks, shared->opt_mem_chunk_size >> 10);
+
+	for (i = 0; i < shared->opt_mem_chunks; i++) {
+		if (shared->prev_map[i]) {
+			munmap((void *)shared->prev_map[i], shared->opt_mem_chunk_size);
+			shared->prev_map[i] = 0;
+		}
+
+		/* If we won't change proto here, the kernel might merge close areas */
+		mem = mmap(NULL, shared->opt_mem_chunk_size,
+			   PROT_READ | PROT_WRITE | ((i % 2) ? PROT_EXEC : 0),
+			   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+
+		if (mem != (void *)MAP_FAILED) {
+			shared->prev_map[i] = (unsigned long)mem;
+			chunks[i] = (unsigned long)mem;
+
+			pr_info("\t\tMap at %lx\n",(unsigned long)mem);
+		} else {
+			pr_info("\t\tCan't map\n");
+
+			shared->err_pid	= sys_gettid();
+			shared->err_no	= -errno;
+			exit(1);
+		}
+	}
+
+	if (shared->opt_mem_fill_mode)
+		dirtify_memory(chunks, shared->opt_mem_chunks,
+			       shared->opt_mem_chunk_size,
+			       shared->opt_mem_fill_mode,
+			       nr_pages);
+
+	if (create_files(shared, fd, shared->opt_files))
+		exit(1);
+
+	if (shared->opt_file_size)
+		dirtify_files(fd, shared->opt_files, shared->opt_file_size);
+
+		pr_trace("releasing\n");
+	pthread_mutex_unlock(&shared->mutex);
+
+	while (1) {
+		sleep(shared->opt_refresh_time);
+		if (shared->opt_mem_cycle_mode)
+			dirtify_memory(chunks, shared->opt_mem_chunks,
+				       shared->opt_mem_chunk_size,
+				       shared->opt_mem_cycle_mode,
+				       nr_pages);
+		if (shared->opt_file_size)
+			dirtify_files(fd, shared->opt_files, shared->opt_file_size);
+	}
+}
+
+static int parse_mem_mode(int *mode, char *opt)
+{
+	if (!strcmp(opt, "all")) {
+		*mode = MEM_FILL_MODE_ALL;
+	} else if (!strcmp(opt, "light")) {
+		*mode = MEM_FILL_MODE_LIGHT;
+	} else if (!strcmp(opt, "dirtify")) {
+		*mode = MEM_FILL_MODE_DIRTIFY;
+	} else {
+		pr_err("Unrecognized option %s\n", opt);
+		return -1;
+	}
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	/* a - 97, z - 122, A - 65, 90 */
+	static const char short_opts[] = "t:d:f:m:c:";
+	static struct option long_opts[] = {
+		{"tasks",	required_argument, 0,	't'},
+		{"dir",		required_argument, 0,	'd'},
+		{"files",	required_argument, 0,	'f'},
+		{"memory",	required_argument, 0,	'm'},
+		{"mem-chunks",	required_argument, 0,	'c'},
+		{"mem-fill",	required_argument, 0,	 10},
+		{"mem-cycle",	required_argument, 0,	 11},
+		{"refresh",	required_argument, 0,	 12},
+		{"file-size",	required_argument, 0,	 13},
+		{ },
+	};
+
+	char workdir[PATH_MAX];
+	int opt, idx, pidfd;
+	char pidbuf[32];
+	int status;
+	pid_t pid;
+	size_t i;
+
+	shared = (void *)mmap(NULL, sizeof(*shared), PROT_READ | PROT_WRITE,
+			      MAP_ANONYMOUS | MAP_SHARED, -1, 0);
+	if ((void *)shared == MAP_FAILED) {
+		pr_err("Failed to setup shared data\n");
+		exit(1);
+	}
+
+	pthread_mutexattr_init(&shared->mutex_attr);
+	pthread_mutexattr_setpshared(&shared->mutex_attr, PTHREAD_PROCESS_SHARED);
+	pthread_mutex_init(&shared->mutex, &shared->mutex_attr);
+
+	/*
+	 * Default options.
+	 */
+	shared->opt_mem_chunks = 1;
+	shared->opt_refresh_time = 1;
+	shared->opt_tasks = 1;
+	shared->opt_mem = 1 << 20ul;
+	memset(shared->prev_fd, 0xff, sizeof(shared->prev_fd));
+
+	while (1) {
+		idx = -1;
+		opt = getopt_long(argc, argv, short_opts, long_opts, &idx);
+		if (opt == -1)
+			break;
+
+		switch(opt) {
+		case 't':
+			shared->opt_tasks = (size_t)atol(optarg);
+			break;
+		case 'f':
+			shared->opt_files = (size_t)atol(optarg);
+			break;
+		case 'm':
+			/* In megabytes */
+			shared->opt_mem = (size_t)atol(optarg) << 20ul;
+			break;
+		case 'c':
+			shared->opt_mem_chunks = (size_t)atol(optarg);
+			break;
+		case 'd':
+			shared->opt_work_dir = optarg;
+			break;
+		case 10:
+			if (parse_mem_mode(&shared->opt_mem_fill_mode, optarg))
+				goto usage;
+		case 11:
+			if (parse_mem_mode(&shared->opt_mem_cycle_mode, optarg))
+				goto usage;
+			break;
+		case 12:
+			shared->opt_refresh_time = (unsigned int)atoi(optarg);
+			break;
+		case 13:
+			shared->opt_file_size = (size_t)atol(optarg);
+		}
+	}
+
+	if (!shared->opt_work_dir) {
+		shared->opt_work_dir = getcwd(workdir, sizeof(workdir));
+		if (!shared->opt_work_dir)	{
+			pr_perror("Can't fetch current working dir");
+			exit(1);
+		}
+		shared->opt_work_dir = workdir;
+	}
+
+	if (shared->opt_mem_chunks > MAX_CHUNK)
+		shared->opt_mem_chunks = MAX_CHUNK;
+
+	if (shared->opt_files > MAX_CHUNK)
+		shared->opt_files = MAX_CHUNK;
+
+	shared->work_dir = opendir(shared->opt_work_dir);
+	if (!shared->work_dir) {
+		pr_perror("Can't open working dir `%s'",
+			  shared->opt_work_dir);
+		exit(1);
+	}
+	shared->work_dir_fd = dirfd(shared->work_dir);
+
+	shared->opt_mem_chunk_size = shared->opt_mem / shared->opt_mem_chunks;
+
+	if (shared->opt_mem_chunk_size &&
+	    shared->opt_mem_chunk_size < PAGE_SIZE) {
+		pr_err("Memory chunk size is too small, provide at least %lu M of memory\n",
+		       (shared->opt_mem_chunks * PAGE_SIZE) >> 20ul);
+		exit(1);
+	}
+
+	for (i = 0; i < shared->opt_tasks; i++) {
+		if (shared->err_no)
+			goto err_child;
+
+		pid = fork();
+		if (pid < 0) {
+			printf("Can't create fork: %m\n");
+			exit(1);
+		} else if (pid == 0) {
+			work_on_fork(shared);
+		}
+	}
+
+	/*
+	 * Once everything is done and we're in cycle,
+	 * create pidfile and go to sleep...
+	 */
+	pid = sys_gettid();
+	pidfd = openat(shared->work_dir_fd, "bers.pid", O_RDWR | O_CREAT | O_TRUNC, 0666);
+	if (pidfd < 0) {
+		pr_perror("Can't open pidfile");
+		exit(1);
+	}
+	snprintf(pidbuf, sizeof(pidbuf), "%d", sys_gettid());
+	write(pidfd, pidbuf, strlen(pidbuf));
+	close(pidfd);
+	pidfd = -1;
+
+	/*
+	 * Endless!
+	 */
+	while (!shared->err_no)
+		sleep(1);
+
+err_child:
+	pr_err("Child %d exited with %d\n",
+	       shared->err_pid, shared->err_no);
+	return shared->err_no;
+}
diff --git a/test/bers/bers.txt b/test/bers/bers.txt
new file mode 100644
index 000000000000..17c0c0800c35
--- /dev/null
+++ b/test/bers/bers.txt
@@ -0,0 +1,74 @@
+bers(8)
+=======
+:doctype:       manpage
+:man source:    bers
+:man version:   0.0.1
+:man manual:    bers manual
+
+NAME
+----
+bers - go berserk and eat computer resources
+
+SYNOPSIS
+--------
+*bers* ['options']
+
+DESCRIPTION
+-----------
+*bers* is a command line utility aimed to eat resources of the computer it runs on.
+Idea behind is to create a number of tasks which would trash computer resources
+eating cpu and i/o time.
+
+OPTIONS
+-------
+*-t*, *--tasks* 'num'::
+	Create 'num' number of forks.
+
+*-d*, *--dir* 'dir'::
+	Path to 'dir' directory where temporary files will be created to load
+	I/O subsystem.
+
+*-f*, *--files* 'num'::
+	Create 'num' files in each task.
+
+*-m*, *--memory* 'num'::
+	Allocate 'num' megabytes of memory for every task.
+
+*--mem-chunks* 'num'::
+	Allocate memory for each task not as one slab but split
+	it into 'num' equal parts.
+
+*--mem-fill* 'mode'::
+	Touch (write) into allocated memory once task is created. The
+	'mode' might be one of the following: 'all' -- write every
+	single byte of the memory, 'light' -- write into first bytes
+	of first page of the allocated memory chunk, 'dirtify' -- write
+	into every page of every allocated chunk.
+
+*--mem-cycle* 'mode'::
+	Same as *--mem-fill*, but 'mode' taken into account while
+	task is cycling. By default each cycle initiated per one second.
+
+*--refresh* 'second'::
+	Refresh load state of every task each 'second'. By refsresh
+	here means to dirtify memory and file contents.
+
+*--file-size* 'bytes'::
+	Write 'bytes' of data into each file on every refresh cycle.
+
+EXAMPLE
+-------
+
+bers -d test/bers/dump -t 256 -m 54 -c 4 -f 200 --mem-fill dirtify --mem-cycle dirtify
+
+We generate 256 tasks wit each allocating 54 megabytes of memory splitted
+equally into 4 memory areas. Each task opens 200 files. On creation and
+cycling we touch every page of every memory area.
+
+AUTHOR
+------
+OpenVZ team.
+
+COPYRIGHT
+---------
+Copyright \(C) 2014, Parallels Inc.
-- 
1.8.3.1



More information about the CRIU mailing list