blob: 95c4a9f7851efb65123e47df9598e5231d8c783b [file] [log] [blame] [raw]
/*
* Copyright (C) 2000-2009, Parallels, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <unistd.h>
#include "types.h"
#include "vzconfig.h"
#include "logger.h"
#include "util.h"
/* #include <linux/magic.h>
* Instead of including a file which is not present in all environments,
* we copy-paste reiserfs magic from it
*/
#define REISERFS_SUPER_MAGIC 0x52654973
#define SYSRSRV 52428800
#define MEMPERVE 5542912
#define LOWPERVE 1348608
#define MAXVAL LONG_MAX
#define DEF_CPUUNITS 1000
#define CHECK_LIMIT(val) (val > MAXVAL) ? MAXVAL: val
#define PROCMEM "/proc/meminfo"
#define PROCTHREADS "/proc/sys/kernel/threads-max"
#define MAX_SL 3
/* maximal per system values */
#define MAX_TOTAL_PIDS 8000
#define MAXIPTENT 2000
/* minimal per CT values */
#define MINPROC 30
#define MINTCPBUF 65536
#define MINDGRAMBUF 32768
#define MINSOCKBUF 2560
#define MIN_PTY 4
#define MIN_IPTENT 4
#define MIN_PGLOCK 4
#define MIN_GUARPG 1024
/* maximal per CT values */
#define PRIVVM_PVE 0.6 / 4096
#define IPTENT_PVE 200
#define FLOCK_PVE 1000
#define PTY_PVE 512
/* default diskspace values */
#define HOST_DS 10485760ULL /* 10 GB */
#define DEF_DS 225280
#define HOST_DI 100000
#define DEF_DI 88000
/* helper values */
#define SHMPG_PRIVVM 0.10
#define NFLOCK_NFILE 0.10
#define NFILE_AVNPROC 32
#define PTY_PROC 0.1
#define PGLOCK_KMEM 204800 /* 2% KMEM */
#define K_KMEM_MAX 2.0
/* limit/barrier delta values */
#define KMEM_DELTA 1.1
#define NFLOCK_DELTA 1.1
#define PRIVVM_DELTA 1.1
#define DCACHE_DELTA 1.03
#define DS_DELTA 1.1
#define DI_DELTA 1.1
#define NUMUBC 23
enum { NPROC = 0,
AVNUMPROC,
NTCPSOCK,
NOTHSOCK,
VMGUAR,
KMEM,
TCPSND,
TCPRCV,
SOCKOTH,
DGRAM,
OOMGUAR,
PRIVVMPG,
LOCKPG,
SHMPG,
NPHYPG,
NFILE,
NFLOCK,
NPTY,
NSIGINFO,
DCACHE,
IPTENT,
DISKSPACE,
DISKINODES
};
char *ubcnames[] = {
"NUMPROC",
"AVNUMPROC",
"NUMTCPSOCK",
"NUMOTHERSOCK",
"VMGUARPAGES",
"KMEMSIZE",
"TCPSNDBUF",
"TCPRCVBUF",
"OTHERSOCKBUF",
"DGRAMRCVBUF",
"OOMGUARPAGES",
"PRIVVMPAGES",
"LOCKEDPAGES",
"SHMPAGES",
"PHYSPAGES",
"NUMFILE",
"NUMFLOCK",
"NUMPTY",
"NUMSIGINFO",
"DCACHESIZE",
"NUMIPTENT",
"DISKSPACE",
"DISKINODES",
NULL};
struct par_limits {
unsigned long long bar;
unsigned long long lim;
};
struct par_limits params[NUMUBC];
/* Global variables */
unsigned long long mem_total, low_total, swap_total, ds_total, di_total;
long pagesize, proc_calc;
int num_ve, ve_allowed, osl, vswap;
float k_kmem[MAX_SL] = {1, 1.2, 1.8};
float k_nproc[MAX_SL] = {1, 1.5, 2};
float k_avnpr[MAX_SL] = {8, 5, 3};
int k_kpp[MAX_SL] = {81920, 65536, 53248};
int k_sock1[MAX_SL] = {262144, 131072, 65536};
int k_sock2[MAX_SL] = {4096, 3036, 2560};
int numsiginfo[MAX_SL] = {1024, 512, 256};
float k_dcache[MAX_SL] = {1.5 * 384, 1.2 * 384, 1 * 384};
float k_privvm[MAX_SL] = {6, 3, 1.5};
int k_pglock[MAX_SL] = {10, 3, 1};
int k_msl[MAX_SL] = {10485760, 2097152, 0};
char *level_string[MAX_SL + 1] = {
"Free resource distribution. Any parameters may be increased",
"Normal resource distribution. Secondary parameters may be increased",
"Partial resource shortage. Auxiliary parameters may be increased",
"Overall resource shortage. Please, do not change any parameters!"
};
static void usage(int rc)
{
fprintf(rc ? stderr : stdout,
"Usage: vzsplit [-f config_name] [-n num_CTs] [-s swap_size] [-v yes|no]\n"
" -f configuration sample name\n"
" -n number of containers\n"
" -s swap space, Kbytes\n"
" -v yes|no generate VSwap config (overrides auto-detection)\n"
);
exit(rc);
}
static void header(FILE *fp)
{
fprintf(fp,
"# Configuration file generated by vzsplit for %d container%c\n"
"# on HN with total amount of physical memory %llu Mb,\n"
"# low memory %llu Mb, swap size %llu Mb, max threads %lu.\n"
"# Resource commit level %d:\n# %s\n",
num_ve, (num_ve == 1)? ' ': 's',
(mem_total >> 20),
(low_total >> 20), (swap_total >> 20), proc_calc,
osl, level_string[osl]
);
return;
}
#define PRINT_PARAM(i) \
fprintf(fp, "%s=\"%s\"\n", \
ubcnames[i], ubcstr( \
(unsigned long) params[i].bar, \
(unsigned long) params[i].lim) )
static int lconv(char *name)
{
int i;
FILE *fp;
if (name != NULL) {
if ((fp = fopen(name, "w")) == NULL) {
logger(-1, errno, "Unable to create %s", name);
return 1;
}
} else {
fp = stdout;
}
header(fp);
if (vswap)
{
unsigned long mem = params[PRIVVMPG].bar;
fprintf(fp, "\n# This is VSwap-enabled configuration\n");
fprintf(fp, "# NOTE this configuration needs OpenVZ kernel "
"042stab042 or newer!\n\n");
fprintf(fp, "PHYSPAGES=\"0:%lu\"\n", mem);
fprintf(fp, "SWAPPAGES=\"0:%lu\"\n", mem * 2);
fprintf(fp, "\n"
"KMEMSIZE=\"unlimited\"\n"
"DCACHESIZE=\"unlimited\"\n"
"LOCKEDPAGES=\"unlimited\"\n"
"PRIVVMPAGES=\"unlimited\"\n"
"SHMPAGES=\"unlimited\"\n"
"NUMPROC=\"unlimited\"\n"
"VMGUARPAGES=\"0:unlimited\"\n"
"OOMGUARPAGES=\"0:unlimited\"\n"
"NUMTCPSOCK=\"unlimited\"\n"
"NUMFLOCK=\"unlimited\"\n"
"NUMPTY=\"unlimited\"\n"
"NUMSIGINFO=\"unlimited\"\n"
"TCPSNDBUF=\"unlimited\"\n"
"TCPRCVBUF=\"unlimited\"\n"
"OTHERSOCKBUF=\"unlimited\"\n"
"DGRAMRCVBUF=\"unlimited\"\n"
"NUMOTHERSOCK=\"unlimited\"\n"
"NUMFILE=\"unlimited\"\n"
"NUMIPTENT=\"unlimited\"\n"
"\n");
PRINT_PARAM(DISKSPACE);
PRINT_PARAM(DISKINODES);
}
else {
fprintf(fp, "# Primary parameters\n");
for (i = 0; i < NUMUBC; i++) {
if (i == KMEM)
fprintf(fp, "\n# Secondary parameters\n");
else if (i ==LOCKPG)
fprintf(fp, "\n# Auxiliary parameters\n");
PRINT_PARAM(i);
}
}
fprintf(fp, "CPUUNITS=\"%d\"\n", DEF_CPUUNITS);
if (name) {
fclose(fp);
logger(-1, 0, "Config %s was created", name);
}
return 0;
}
static int calculate_values()
{
long long rest, delta, low_pve, tot_pve;
long long kmem, numproc, avnproc, sockbuf, tcpbuf, iptent, numfile;
long long dcache, numflock;
long long lockedpages, numpty, guarpg, privvm, shmpg, di_pve, ds_pve;
int sl;
sl = avnproc = tcpbuf = 0;
tot_pve = (mem_total + swap_total - SYSRSRV) / num_ve;
for (osl = 0; osl < MAX_SL; osl++) {
numproc = k_nproc[osl] * proc_calc / num_ve;
low_pve = low_total * 0.4 * k_kmem[osl] / num_ve;
guarpg = (tot_pve - low_pve) / pagesize;
if (guarpg < MIN_GUARPG) {
guarpg = MIN_GUARPG;
low_pve = tot_pve - guarpg * pagesize;
}
kmem = low_pve / 2;
if (low_pve < k_msl[osl])
continue;
if (numproc < MINPROC)
numproc = MINPROC;
avnproc = kmem / k_kpp[osl];
if (avnproc < MINPROC / 2)
continue;
if (numproc < avnproc)
numproc = avnproc;
if ((numproc < 2 * avnproc) && (osl < MAX_SL))
numproc = 2 * avnproc;
if (numproc > avnproc * k_avnpr[osl])
numproc = avnproc * k_avnpr[osl];
sockbuf = low_pve - kmem;
tcpbuf = sockbuf / 3;
if (tcpbuf < k_sock1[osl] + k_sock2[osl] * numproc)
continue;
sl = osl;
break;
}
if (osl == MAX_SL) {
sl = osl - 1;
numproc = k_nproc[sl] * proc_calc / num_ve;
low_pve = low_total * 0.4 * K_KMEM_MAX / num_ve;
guarpg = (tot_pve - low_pve) / pagesize;
if (guarpg < MIN_GUARPG) {
guarpg = MIN_GUARPG;
low_pve = tot_pve - guarpg * pagesize;
}
do {
numproc /= 2;
if (numproc < MINPROC)
numproc = MINPROC;
avnproc = numproc / 2;
delta = MINSOCKBUF * numproc;
sockbuf = 2 * (MINDGRAMBUF + MINTCPBUF + delta);
kmem = low_pve - sockbuf;
rest = kmem - avnproc * k_kpp[sl];
} while ((rest < 0) && (numproc > MINPROC));
if (rest < 0) {
logger(-1, 0, "Fatal resource shortage, try to "
"decrease the number of containers");
return 1;
}
params[DGRAM].lim = params[DGRAM].bar = MINDGRAMBUF;
params[SOCKOTH].bar = MINDGRAMBUF;
params[SOCKOTH].lim = MINDGRAMBUF + delta;
params[TCPSND].bar = MINTCPBUF;
params[TCPSND].lim = MINTCPBUF + delta;
params[TCPRCV].bar = MINTCPBUF;
if (rest > delta) {
params[TCPRCV].lim = MINTCPBUF + delta;
kmem += rest - delta;
} else
params[TCPRCV].lim = MINTCPBUF + rest;
} else {
delta = k_sock2[sl] * numproc;
rest = tcpbuf - delta;
params[TCPSND].lim = params[TCPRCV].lim = CHECK_LIMIT(tcpbuf);
params[TCPSND].bar = params[TCPRCV].bar = CHECK_LIMIT(rest);
params[DGRAM].lim = params[DGRAM].bar =
params[SOCKOTH].bar = CHECK_LIMIT(rest / 2);
params[SOCKOTH].lim = CHECK_LIMIT(rest / 2 + delta);
}
params[KMEM].lim = CHECK_LIMIT(kmem * KMEM_DELTA);
params[KMEM].bar = CHECK_LIMIT(kmem);
params[NPROC].lim = params[NPROC].bar = CHECK_LIMIT(numproc);
params[AVNUMPROC].lim = params[AVNUMPROC].bar = CHECK_LIMIT(avnproc);
params[NTCPSOCK].lim = params[NTCPSOCK].bar = CHECK_LIMIT(numproc);
params[NOTHSOCK].lim = params[NOTHSOCK].bar = CHECK_LIMIT(numproc);
iptent = MAXIPTENT / num_ve;
if (iptent > IPTENT_PVE)
iptent = IPTENT_PVE;
else if (iptent < MIN_IPTENT)
iptent = MIN_IPTENT;
params[IPTENT].lim = params[IPTENT].bar = iptent;
numfile = avnproc * NFILE_AVNPROC;
params[NFILE].lim = params[NFILE].bar = CHECK_LIMIT(numfile);
dcache = numfile * k_dcache[sl];
params[DCACHE].lim = CHECK_LIMIT(dcache);
params[DCACHE].bar = CHECK_LIMIT(dcache / DCACHE_DELTA);
numflock = numfile * NFLOCK_NFILE;
if (numflock > FLOCK_PVE)
numflock = FLOCK_PVE;
params[NFLOCK].lim = CHECK_LIMIT(numflock * NFLOCK_DELTA);
params[NFLOCK].bar = numflock;
lockedpages = kmem * k_pglock[sl] / PGLOCK_KMEM;
if (lockedpages < MIN_PGLOCK)
lockedpages = MIN_PGLOCK;
params[LOCKPG].lim = params[LOCKPG].bar = CHECK_LIMIT(lockedpages);
numpty = numproc * PTY_PROC;
if (numpty < MIN_PTY)
numpty = MIN_PTY;
if (numpty > PTY_PVE)
numpty = PTY_PVE;
params[NPTY].lim = params[NPTY].bar = numpty;
params[NSIGINFO].lim = params[NSIGINFO].bar = numsiginfo[sl];
params[NPHYPG].bar = 0; params[NPHYPG].lim = MAXVAL;
privvm = guarpg * k_privvm[sl];
if (privvm > tot_pve / pagesize)
privvm = guarpg;
if (num_ve > 1 && privvm > PRIVVM_PVE * mem_total) {
privvm = PRIVVM_PVE * mem_total;
if (guarpg > privvm)
guarpg = privvm;
}
params[PRIVVMPG].bar = CHECK_LIMIT(privvm);
params[PRIVVMPG].lim = CHECK_LIMIT(privvm * PRIVVM_DELTA);
params[VMGUAR].bar = params[OOMGUAR].bar = CHECK_LIMIT(guarpg);
params[VMGUAR].lim = params[OOMGUAR].lim = MAXVAL;
shmpg = privvm * SHMPG_PRIVVM;
params[SHMPG].bar = params[SHMPG].lim = CHECK_LIMIT(shmpg);
if (ds_total == 0) {
ds_pve = DEF_DS;
di_pve = DEF_DI;
} else {
ds_pve = ds_total / (2 * num_ve);
di_pve = di_total / (2 * num_ve);
}
params[DISKSPACE].bar = ds_pve / DS_DELTA;
params[DISKSPACE].lim = ds_pve;
params[DISKINODES].bar = di_pve / DI_DELTA;
params[DISKINODES].lim = di_pve;
return 0;
}
static char * get_ve_private()
{
vps_param *param;
char *ve_private, *veidp, *ret;
param = init_vps_param();
/* Parse global config file */
vps_parse_config(0, GLOBAL_CFG, param, NULL);
ve_private = param->res.fs.private_orig;
if (ve_private == NULL) {
free_vps_param(param);
return NULL;
}
/* Remove $VEID and beyond from the string */
veidp = strstr(ve_private, "$VEID");
if (veidp == NULL)
veidp = strstr(ve_private, "${VEID}");
if (veidp != NULL)
*veidp = '\0';
ret = strdup(ve_private);
free_vps_param(param);
return ret;
}
static int check_disk_space() {
char * ve_private;
int nofs = 0, noinodes = 0;
long ve_ds, ve_di;
int rec = 0, retval = 0;
struct statfs statfs_buf;
ve_private = get_ve_private();
if (ve_private == NULL) {
logger(-1, 0, "WARNING: unable to get VE_PRIVATE value "
"from " GLOBAL_CFG ".");
nofs = 1;
}
else {
if (statfs(ve_private, &statfs_buf) < 0) {
logger(-1, errno, "WARNING: statfs on %s failed",
ve_private);
nofs = 1;
}
}
if (nofs == 1) {
logger(-1, 0, "Default disk space values to be used.\n");
ds_total = 0; di_total = 0;
return retval;
}
ds_total = statfs_buf.f_blocks;
/* convert to Kbytes */
ds_total *= (statfs_buf.f_bsize / 1024);
di_total = statfs_buf.f_files;
if (statfs_buf.f_type == REISERFS_SUPER_MAGIC) {
/* reiserfs does not have inodes, thus
* no limit on number of files */
noinodes = 1;
}
if (ds_total / 2 < HOST_DS) {
rec = 1;
ds_total /= 2;
} else
ds_total -= HOST_DS;
if (noinodes != 1) {
if (di_total / 2 < HOST_DI) {
rec = 1;
di_total /= 2;
} else
di_total -= HOST_DI;
}
if (rec)
logger(-1, 0, "WARNING: Recommended minimal size "
"of partition holding %s is %llu Gb!",
ve_private, HOST_DS / 1024 / 1024 * 2);
ve_ds = ds_total / DEF_DS;
ve_di = di_total / DEF_DI;
if (ve_ds < num_ve) {
retval = 1;
ve_allowed = ve_ds;
}
if ((noinodes != 1) && (ve_di < num_ve) ) {
retval = 1;
if (ve_di < ve_ds)
ve_allowed = ve_di;
}
if (retval == 1) {
logger(-1, 0, "WARNING: partition holding %s do not "
"have space required for %d containers\n"
"The maximum allowed value is %d",
ve_private, num_ve, ve_allowed);
logger(-1, 0, "Default disk space values "
"will be used\n");
ds_total = 0; di_total = 0;
}
free(ve_private);
return retval;
}
int main(int argc, char **argv)
{
int len, opt, swp;
char *tail;
char *name = NULL;
struct stat st;
FILE *fd;
char str[1024];
unsigned long long val;
int retval;
num_ve = -1;
swp = 0;
mem_total = 0;
low_total = 0;
swap_total = 0;
init_log(NULL, 0, 1, 0, 0, "vzsplit");
vswap = is_vswap_mode();
while ((opt = getopt(argc, argv, "f:n:s:h:v:")) > 0) {
switch(opt) {
case 'v':
switch (yesno2id(optarg)) {
case YES:
vswap = 1;
break;
case NO:
vswap = 0;
break;
default:
logger(-1, 0, "Invalid argument "
"for -v: %s", optarg);
usage(1);
}
break;
case 'f':
len = strlen(optarg) + strlen(VPSCONFDIR) +
strlen("/ve-.conf-sample");
name = (char *)vz_malloc(len + 1);
if (!name)
exit(1);
sprintf(name, VPSCONFDIR "/ve-%s.conf-sample", optarg);
if (!stat(name, &st)) {
logger(-1, 0, "File %s already exist",
name);
exit(1);
}
break;
case 'n':
num_ve = strtol(optarg, &tail, 10);
if (*tail != '\0') {
logger(-1, 0, "Invalid argument "
"for -n: %s", optarg);
usage(1);
}
break;
case 's':
if (optarg[0] == '-') {
logger(-1, 0, "Negative value for -s");
usage(1);
}
swp = 1;
swap_total = strtoll(optarg, &tail, 10);
swap_total <<= 10;
if (*tail != '\0') {
logger(-1, 0, "Invalid argument "
"for -s: %s", optarg);
usage(1);
}
break;
case 'h':
usage(0);
break;
default :
usage(1);
}
}
if (optind < argc) {
usage(1);
}
if (num_ve == -1) {
printf("Enter the number of containers: ");
if (scanf("%d", &num_ve) != 1) {
logger(-1, 0, "Invalid value.");
exit(1);
}
}
if (num_ve < 1) {
logger(-1, 0, "Incorrect value for number of containers.");
exit(1);
}
if ((fd = fopen(PROCMEM, "r")) == NULL) {
logger(-1, errno, "Cannot open " PROCMEM);
exit(1);
}
while (fgets(str, sizeof(str), fd))
if (sscanf(str, "MemTotal:\t %llu", &val) == 1)
mem_total = val << 10;
else if (sscanf(str, "LowTotal:\t %llu", &val) == 1)
low_total = val << 10;
else if (!swp && sscanf(str, "SwapTotal:\t %llu", &val) == 1)
swap_total = val << 10;
fclose(fd);
if (low_total == 0)
low_total = mem_total;
if (mem_total < SYSRSRV) {
logger(-1, 0, "At least %d Mb of RAM should be "
"installed on Hardware Node",
SYSRSRV / 1024 / 1024);
exit(1);
}
if (swap_total > 2 * mem_total)
logger(-1, 0, "The optimal swap space size is %llu Mb, "
"twice bigger than the RAM size\n",
(2 * mem_total) >> 20);
ve_allowed = num_ve;
retval = 0;
if (((mem_total + swap_total - SYSRSRV) / num_ve) < MEMPERVE) {
logger(-1, 0, "On node with %llu Mb of memory (RAM + swap) "
"%d containers can not be allocated",
(mem_total + swap_total) >> 20, num_ve);
ve_allowed = (mem_total + swap_total - SYSRSRV) / MEMPERVE;
retval = 1;
}
if (((low_total - SYSRSRV)/ ve_allowed) < LOWPERVE) {
int ve_low;
logger(-1, 0, "On node with %llu Mb of Low Memory "
"%d containers can not be allocated",
low_total >> 20, num_ve);
ve_low = (low_total - SYSRSRV) / LOWPERVE;
if (ve_low < ve_allowed)
ve_allowed = ve_low;
retval = 1;
}
if (retval != 0) {
logger(-1, 0, "The maximum allowed value is %d",
ve_allowed);
exit(retval);
}
if ((fd = fopen(PROCTHREADS, "r")) == NULL) {
logger(-1, errno, "Cannot open " PROCTHREADS);
exit(1);
}
if (fgets(str, sizeof(str), fd))
if (sscanf(str, "%llu", &val) == 1)
proc_calc = val;
fclose(fd);
retval = check_disk_space();
if ((pagesize = sysconf(_SC_PAGE_SIZE)) == -1)
pagesize = 4096;
if (low_total > 2U << 30) {
if (proc_calc > 2 * MAX_TOTAL_PIDS)
proc_calc = 2 * MAX_TOTAL_PIDS;
} else if (proc_calc > MAX_TOTAL_PIDS)
proc_calc = MAX_TOTAL_PIDS;
if (calculate_values())
exit(1);
retval = lconv(name);
exit(retval);
}