/* $Id: lb.c 795 2013-04-22 05:12:27Z aqua $ * * lookbusy -- a tool for producing synthetic CPU, memory consumption and * disk loads on a host. * * Copyright (c) 2006, Devin Carraway . * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ /* and, redundantly ... */ static const char *copyright = "Copyright (c) 2006-2008, by Devin Carraway \n" "$Id: lb.c 795 2013-04-22 05:12:27Z aqua $\n" "\n" "This program is free software; you can redistribute it and/or modify\n" "it under the terms of the GNU General Public License as published by\n" "the Free Software Foundation; either version 2 of the License, or\n" "(at your option) any later version.\n" "\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n" "\n" "You should have received a copy of the GNU General Public License\n" "along with this program; if not, write to the Free Software\n" "Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA\n" ""; #ifdef HAVE_CONFIG_H #include "config.h" #else #define VERSION "1.4" #define PACKAGE "lookbusy" #endif #include #ifdef HAVE_FCNTL_H #include #endif #include #ifdef HAVE_UNISTD_H #include #endif #define _GNU_SOURCE #include #ifdef HAVE_STRING_H #include #endif #include #ifdef HAVE_SYS_WAIT_H #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_SYS_STAT_H #include #endif #include #include #include #include #include #include #include #include #ifndef HAVE_STRTOL #define strtol(x,e,b) atol(x) #endif #ifndef HAVE_STRTOLL #define strtoll(x,e,b) atol(x) #endif #ifdef HAVE_SYSCONF #define LB_PAGE_SIZE sysconf(_SC_PAGESIZE) #else #define LB_PAGE_SIZE 4096 #endif #ifdef _FILE_OFFSET_BITS #if _FILE_OFFSET_BITS == 64 #define HAVE_LFS 1 #endif #else #define HAVE_LFS (sizeof(off_t) >= 8) #endif #define PI 3.14159265358979323846 enum cpu_util_mode { UTIL_MODE_FIXED = 0, UTIL_MODE_CURVE }; enum cpu_util_mode c_cpu_util_mode = UTIL_MODE_FIXED; static int utc = 0; static int c_cpu_curve_period = 86400; /* seconds */ static int c_cpu_curve_peak = 60 * 60 * 13; /* 1PM local time */ static int c_cpu_util_l = 50, c_cpu_util_h = 50; /* percent */ static size_t c_mem_util = 0; /* bytes */ static long c_mem_stir_sleep = 1000; /* 1000 usec / 1 ms */ static off_t c_disk_util = 0; /* MB */ static char **c_disk_churn_paths; static size_t c_disk_churn_paths_n; static size_t c_disk_churn_block_size = 32 * 1024; /* bytes */ static size_t c_disk_churn_step_size = 4 * 1024; /* bytes */ static long c_disk_churn_sleep = 100; /* ms */ static int ncpus = -1; /* autodetect */ static int verbosity = 1; static char *mem_stir_buffer; static pid_t *cpu_pids; static size_t n_cpu_pids; static pid_t *disk_pids; static size_t n_disk_pids; static pid_t mem_pid; typedef void (*spinner_fn)(long long, long long, long long, void*, void *); static int say(int level, const char *fmt, ...) { va_list ap; int r; if (level > verbosity) return 0; va_start(ap, fmt); r = vprintf(fmt, ap); va_end(ap); return r; } static int err(const char *fmt, ...) { va_list ap; int r; va_start(ap, fmt); r = vfprintf(stderr, fmt, ap); va_end(ap); return r; } static int parse_timespan(const char *str, int *r) { regex_t ex; int e; static const char *pattern = "^[[:space:]]*([0-9]+)([dhms]?)[[:space:]]*$"; if ((e = regcomp(&ex,pattern, REG_EXTENDED)) != 0) { char errbuf[128]; regerror(e, &ex, errbuf, sizeof(errbuf)-1); errbuf[sizeof(errbuf)-1] = '\0'; err("regcomp(%s): %s\n", pattern, errbuf); return -1; } regmatch_t matches[3]; if ((e = regexec(&ex, str, 3, matches, 0)) != 0) { char errbuf[128]; regerror(e, &ex, errbuf, sizeof(errbuf)-1); errbuf[sizeof(errbuf)-1] = '\0'; if (e != REG_NOMATCH) err("regexec: Couldn't match '%s' in '%s': %s\n", pattern, str, errbuf); return -1; } *r = (int)strtol(str + matches[1].rm_so, NULL, 10) * (matches[2].rm_so == -1 ? 1 : *(str + matches[2].rm_so) == 'd' ? 86400 : *(str + matches[2].rm_so) == 'h' ? 3600 : *(str + matches[2].rm_so) == 'm' ? 60 : 1); return 0; } static int parse_large_size(const char *str, off_t *r) { regex_t ex; int e; static const char *pattern = HAVE_LFS ? "^[[:space:]]*([0-9]+)(b|kb?|mb?|gb?|tb?)?[[:space:]]*$" : "^[[:space:]]*([0-9]+)(b|kb?|mb?|gb?)?[[:space:]]*$"; if ((e = regcomp(&ex,pattern, REG_EXTENDED | REG_ICASE)) != 0) { char errbuf[128]; regerror(e, &ex, errbuf, sizeof(errbuf)-1); errbuf[sizeof(errbuf)-1] = '\0'; err("regcomp(%s): %s\n", pattern, errbuf); return -1; } regmatch_t matches[3]; if ((e = regexec(&ex, str, 3, matches, 0)) != 0) { char errbuf[128]; regerror(e, &ex, errbuf, sizeof(errbuf)-1); errbuf[sizeof(errbuf)-1] = '\0'; if (e != REG_NOMATCH) err("regexec: Couldn't match '%s' in '%s': %s\n", pattern, str, errbuf); return -1; } *r = (off_t)strtoll(str + matches[1].rm_so, NULL, 10) * (matches[2].rm_so == -1 ? 1 : tolower(*(str + matches[2].rm_so)) == 't' ? (off_t)1 << 40 : tolower(*(str + matches[2].rm_so)) == 'g' ? (off_t)1 << 30 : tolower(*(str + matches[2].rm_so)) == 'm' ? (off_t)1 << 20 : tolower(*(str + matches[2].rm_so)) == 'k' ? (off_t)1 << 10 : 1); return 0; } static int parse_size(const char *str, size_t *r) { regex_t ex; int e; static const char *pattern = "^[[:space:]]*([0-9]+)(b|kb?|mb?|gb?)?[[:space:]]*$"; if ((e = regcomp(&ex,pattern, REG_EXTENDED | REG_ICASE)) != 0) { char errbuf[128]; regerror(e, &ex, errbuf, sizeof(errbuf)-1); errbuf[sizeof(errbuf)-1] = '\0'; err("regcomp(%s): %s\n", pattern, errbuf); return -1; } regmatch_t matches[3]; if ((e = regexec(&ex, str, 3, matches, 0)) != 0) { char errbuf[128]; regerror(e, &ex, errbuf, sizeof(errbuf)-1); errbuf[sizeof(errbuf)-1] = '\0'; if (e != REG_NOMATCH) err("regexec: Couldn't match '%s' in '%s': %s\n", pattern, str, errbuf); return -1; } *r = (size_t)strtoll(str + matches[1].rm_so, NULL, 10) * (matches[2].rm_so == -1 ? 1 : tolower(*(str + matches[2].rm_so)) == 'g' ? 1024 * 1024 * 1024 : tolower(*(str + matches[2].rm_so)) == 'm' ? 1024 * 1024 : tolower(*(str + matches[2].rm_so)) == 'k' ? 1024 : 1); return 0; } static int parse_int_range(const char *str, int *start, int *end) { regex_t ex; int e; static const char *pattern = "^[[:space:]]*([0-9]+)[[:space:]]*(-([0-9]+))?[[:space:]]*$"; if ((e = regcomp(&ex,pattern, REG_EXTENDED)) != 0) { char errbuf[128]; regerror(e, &ex, errbuf, sizeof(errbuf)-1); errbuf[sizeof(errbuf)-1] = '\0'; err("regcomp(%s): %s\n", pattern, errbuf); return -1; } regmatch_t matches[4]; if ((e = regexec(&ex, str, 4, matches, 0)) != 0) { char errbuf[128]; regerror(e, &ex, errbuf, sizeof(errbuf)-1); errbuf[sizeof(errbuf)-1] = '\0'; if (e != REG_NOMATCH) err("regexec: Couldn't match '%s' in '%s': %s\n", pattern, str, errbuf); return -1; } *start = (int)strtol(str + matches[1].rm_so, NULL, 10); if (matches[3].rm_so != -1) *end = (int)strtol(str + matches[3].rm_so, NULL, 10); else *end = *start; return 0; } static void shutdown() { if (cpu_pids != NULL) { int i; for (i = 0; i < n_cpu_pids; i++) { if (cpu_pids[i] != 0) { say(1, "killing CPU spinner %d (PID %d)\n", i, cpu_pids[i]); kill(cpu_pids[i], SIGTERM); } } free(cpu_pids); cpu_pids = NULL; } if (mem_pid != 0) { say(1, "killing mem spinner %d\n", mem_pid); kill(mem_pid, SIGTERM); } if (mem_stir_buffer != NULL) { free(mem_stir_buffer); mem_stir_buffer = NULL; } if (disk_pids != NULL) { int i; for (i = 0; i < n_disk_pids; i++) { if (disk_pids[i] != 0) { say(1,"killing disk churner %d (PID %d)\n", i, disk_pids[i]); kill(disk_pids[i], SIGTERM); } } free(disk_pids); disk_pids = NULL; } if (c_disk_churn_paths != NULL) { size_t i; for (i = 0; i < c_disk_churn_paths_n; i++) { if (c_disk_churn_paths[i] != NULL) { if (unlink(c_disk_churn_paths[i]) == -1 && errno != ENOENT) { perror(c_disk_churn_paths[i]); } } } } exit(0); } static RETSIGTYPE sigchld_handler(int signum) { int status; pid_t which = wait(&status); err("got SIGCHLD for dead child %d", which); if (WIFSIGNALED(status)) { err("; exited with signal %d\n", WTERMSIG(status)); } else { err("; exited with status %d\n", WEXITSTATUS(status)); } shutdown(); } static void sigterm_handler(int signum) { shutdown(); } static uint64_t jiffies_to_usec(uint64_t jiffies) { return jiffies * (1000 / sysconf(_SC_CLK_TCK)) * 1000; } static int get_cpu_count() { /* FIXME: linux-specific */ FILE *f; char s[128]; int n = 0; static const char *pattern = "^physical *id +: *0*[1-9]"; regex_t ex; int e; if ((e = regcomp(&ex, pattern, REG_EXTENDED | REG_ICASE)) != 0) { char errbuf[128]; regerror(e, &ex, errbuf, sizeof(errbuf)-1); errbuf[sizeof(errbuf)-1] = '\0'; err("regcomp(%s): %s\n", pattern, errbuf); return -1; } f = fopen("/proc/cpuinfo", "r"); if (f == NULL) { perror("/proc/cpuinfo"); exit(1); } while (fgets(s, sizeof(s)-1, f) != NULL) { s[sizeof(s)-1] = '\0'; if (!strncmp(s, "processor", strlen("processor"))) { n++; } else { e = regexec(&ex, s, 0, NULL, 0); if (e == 0) { /* matched a phys-id other than zero, don't count it */ n--; } else if (e != REG_NOMATCH) { char errbuf[128]; regerror(e, &ex, errbuf, sizeof(errbuf)-1); errbuf[sizeof(errbuf)-1] = '\0'; err("regexec: Couldn't match '%s' in '%s': %s\n", pattern, s, errbuf); return -1; } } } fclose(f); if (n == 0) { err("Couldn't get any CPU counts, assuming one CPU core\n"); err("If this is wrong, override with --ncpus\n"); ncpus = 1; } return n; } static uint64_t get_cpu_busy_time() { FILE *f; char s[256]; uint64_t utime, ntime, stime; static regex_t *ex = NULL; static const char *pattern = "^cpu[[:space:]]+([0-9]+)[[:space:]]+([0-9]+)[[:space:]]+([0-9]+)"; int r; if (ex == NULL) { ex = (regex_t *)malloc(sizeof(*ex)); if (ex == NULL) { perror("malloc"); _exit(1); } if ((r = regcomp(ex,pattern, REG_EXTENDED)) != 0) { char errbuf[128]; regerror(r, ex, errbuf, sizeof(errbuf)-1); errbuf[sizeof(errbuf)-1] = '\0'; err("regcomp(%s): %s\n", pattern, errbuf); _exit(1); } } if ((f = fopen("/proc/stat", "r")) == NULL) { perror("/proc/stat"); _exit(1); } if (fgets(s, sizeof(s)-1, f) == NULL) { perror("fgets"); _exit(1); } regmatch_t matches[4]; if ((r = regexec(ex, s, 4, matches, 0)) != 0) { char errbuf[128]; regerror(r, ex, errbuf, sizeof(errbuf)-1); errbuf[sizeof(errbuf)-1] = '\0'; err("regexec: Couldn't match '%s' in '%s': %s\n", pattern, s, errbuf); _exit(1); } utime = atol(s + matches[1].rm_so); ntime = atol(s + matches[2].rm_so); stime = atol(s + matches[3].rm_so); say(3, "cpu_spin(%d): current utime=%"PRIu64 " ntime=%"PRIu64" stime=%"PRIu64" total=%"PRIu64"\n", getpid(), utime, ntime, stime, utime + ntime + stime); fclose(f); return utime + ntime + stime; } static char cpu_spin_accumulator; static char squander_time(uint64_t iteration) { return (cpu_spin_accumulator += (char)iteration); } static void cpu_spin_calibrate(int util, uint64_t *busycount, suseconds_t *sleeptime) { uint64_t counter; struct timeval tv, tv2; const uint64_t iterations = 10000000; say(1, "cpu_spin (%d): measuring CPU\n", getpid()); if (gettimeofday(&tv, NULL) == -1) shutdown(); for (counter = 0; counter < iterations; counter++) { squander_time(counter); } if (gettimeofday(&tv2, NULL) == -1) shutdown(); long long elapsed = (tv2.tv_sec - tv.tv_sec) * 1000000 + (tv2.tv_usec - tv.tv_usec); long long countspeed = (counter * 100000)/elapsed; *busycount = (countspeed * util) / 100LL; /* busy_count_time = busycount/(counter/elapsed) * idle_time = 1sec - busy_count_time */ *sleeptime = 100000 - *busycount / (counter / elapsed); say(3, "cpu_spin (%d): %"PRIu64" iterations in %lld usec (%lld/sec)\n", getpid(), counter, elapsed, countspeed); say(1, "cpu_spin (%d): est. %d%% util at %"PRIu64" cycles, %u usec sleep\n", getpid(), util, *busycount, *sleeptime); } static double cpu_spin_compute_util(enum cpu_util_mode mode, int l, int h, time_t curtime) { static long time_offset = -1; if (time_offset == -1) { if (!utc) { #ifdef HAVE_TZSET tzset(); #endif time_offset = -timezone; } else { time_offset = 0; } time_offset += c_cpu_curve_peak; } if (mode == UTIL_MODE_FIXED) return l; if (mode == UTIL_MODE_CURVE) { if (curtime == 0) curtime = time(NULL); long offset_in_interval = (curtime + time_offset) % c_cpu_curve_period; double fraction = (double)offset_in_interval / (double)c_cpu_curve_period; /* model the CPU usage as a cosine function over [0,2pi], shifted up * by min+1 to place the floor on min, and scaled so as to have its * entire range between min and max. That is, the peak of the cosine * curve is placed at X=0, and it oscillates over the selected CPU * utilization range. * * This isn't a particularly realistic utilization curve and something * better ought to be found. Or just bite the bullet and make it * data-driven. */ double level = (double)l + (double)(h - l) * (cos(fraction * PI * 2) + 1)/2; return level; } /* shouldn't get here */ return -1; } static void cpu_spin(long long ncpus, long long util_l, long long util_h, void *dummy, void *dummy2) { uint64_t busycount; const uint64_t minimum_cycles = 10000; suseconds_t sleeptime; int first = 1; double util; int64_t adjust = 0; util = cpu_spin_compute_util(c_cpu_util_mode, util_l, util_h, 0); cpu_spin_calibrate(util, &busycount, &sleeptime); say(2, "cpu_spin (%d): spinning cpu\n", getpid()); while (1) { struct timeval tv; long long counter; uint64_t busytime, busytime2; uint64_t walltime, walltime2; if (! first) { uint64_t busy = jiffies_to_usec(busytime2 - busytime) / ncpus; uint64_t wall = walltime2 - walltime; double actual = (100 * busy) / wall; int64_t oldadjust = adjust; /* On a multi-CPU system there will be more than one spinner * trying to hit the target utilization, so restrain the * adjustment range so as to keep from collectively * overcompensating */ adjust = (int64_t)(((util - actual) * busycount) / 100. / ncpus); say(3, "cpu_spin (%d): last iter: count=%lld" " (~%lld of %lld); adjust=%lld\n", getpid(), busycount, busy, wall, adjust); if (adjust < 0 && busycount < -adjust) { say(2, "cpu_spin (%d): usage at lower limit\n", getpid()); busycount = minimum_cycles; } else if (adjust > 0 && UINT64_MAX - adjust < busycount) { say(2, "cpu_spin (%d): usage at upper limit\n", getpid()); } else { busycount += adjust; if (busycount < minimum_cycles) busycount = minimum_cycles; } if ((adjust < 0 && oldadjust > 0) || (adjust > 0 && oldadjust < 0)) { say(3, "cpu_spin (%d): adjusted approximately correctly\n", getpid()); say(3, "cpu_spin (%d): spin count %"PRIu64"\n", getpid(), busycount); } } else { first = 0; } gettimeofday(&tv, NULL); walltime = tv.tv_sec * 1000000 + tv.tv_usec; busytime = get_cpu_busy_time(); say(3, "cpu_spin (%d): spinning (0 to %"PRIu64")...\n", getpid(), busycount); for (counter = 0; counter < busycount; counter++) { squander_time(counter); } say(3, "cpu_spin (%d): sleeping...\n", getpid()); usleep(sleeptime); gettimeofday(&tv, NULL); walltime2 = tv.tv_sec * 1000000 + tv.tv_usec; busytime2 = get_cpu_busy_time(); util = cpu_spin_compute_util(c_cpu_util_mode, util_l, util_h, tv.tv_sec); /* "elapsed" here doesn't necessarily refer only to our own usage */ say(2, "cpu_spin (%d): %"PRIu64" iterations; %"PRIu64" CPU-jiffies elapsed\n", getpid(), counter, busytime2-busytime, ncpus); } _exit(1); } static void mem_stir(long long asz, long long dummy, long long dummy2, void *dummyp, void *dummyp2) { char *mem_stir_buffer; char *p; const size_t pagesize = LB_PAGE_SIZE; const size_t sz = asz; say(1, "mem_stir (%d): stirring %llu bytes...\n", getpid(), sz); if ((mem_stir_buffer = (char *)malloc(sz)) == NULL) { perror("malloc"); _exit(1); } say(2, "mem_stir (%d): dirtying buffer...", getpid()); for (p = mem_stir_buffer; p < mem_stir_buffer + sz; p++) { *p = (char)((uintptr_t)p & 0xff); } say(2, "done\n"); char *sp = mem_stir_buffer; char *dp = mem_stir_buffer; while (1) { const size_t copysz = pagesize; #ifdef HAVE_MEMMOVE memmove(dp, sp, copysz); #else memcpy(dp, sp, copysz); #endif sp += pagesize * 1; dp += pagesize * 5; if (sp >= mem_stir_buffer + sz) { say(2, "mem_stir (%d): read position wrapped\n", getpid()); sp = mem_stir_buffer; } if (dp >= mem_stir_buffer + sz) { say(2, "mem_stir (%d): write position wrapped\n", getpid()); dp = mem_stir_buffer; } usleep(c_mem_stir_sleep); } _exit(1); } static void disk_churn(long long dummy0, long long dummy, long long dummy2, void *pathv, void *szv) { char *path = (char *)pathv; off_t sz = *(off_t *)szv; int fd; int witer; off_t rpos, wpos; say(1, (sizeof(off_t) == 8 ? "disk_churn (%d): churning disk on %s (%ld bytes)\n" : "disk_churn (%d): churning disk on %s (%d bytes)\n"), getpid(), path, sz); if ((fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0600)) == -1) { err("disk_churn (%d): Couldn't create %s: %s\n", getpid(), path, strerror(errno)); exit(1); } if (0 && unlink(path) && errno != ENOENT) { perror(path); exit(1); } if (lseek(fd, sz - sizeof(fd), SEEK_SET) == (off_t)-1) { perror("lseek"); exit(1); } if (write(fd, &fd, sizeof(fd)) == -1) { perror("write"); exit(1); } char *block = malloc(c_disk_churn_block_size); if (block == NULL) { perror("malloc"); _exit(1); } witer = 0; rpos = wpos = 0; while (1) { size_t p; ssize_t r; if (rpos >= sz) { say(2, "disk_churn (%d) reader reached EOF at %ld\n", getpid(), (long)rpos); rpos = 0; } if (wpos >= sz) { say(2, "disk_churn (%d) writer reached EOF at %ld\n", getpid(), (long)wpos); wpos = 0; witer++; } for (p = 0; p < c_disk_churn_block_size; p++) block[p] = (char)((witer | (int)p) & 0xff); if (lseek(fd, wpos, SEEK_SET) == (off_t)-1) { perror("lseek"); exit(1); } if (write(fd, block, c_disk_churn_block_size) == -1) { perror("write"); exit(1); } wpos += c_disk_churn_step_size * 2; if (lseek(fd, rpos, SEEK_SET) == (off_t)-1) { perror("lseek"); exit(1); } r = read(fd, block, c_disk_churn_block_size); if (r == -1) { err("disk_churn (%d): error reading from %s at %ld: %s\n", getpid(), path, (long)rpos, strerror(errno)); exit(1); } if (r == 0) { say(1, "disk_churn (%d): reader reached EOF early (at %ld)\n", getpid(), (long)rpos); rpos = 0; } rpos += c_disk_churn_step_size; usleep(c_disk_churn_sleep * 1000); } _exit(0); } static pid_t fork_and_call(char *desc, spinner_fn fn, long long arg1, long long arg2, long long arg3, void *argP, void *argP2) { pid_t p = fork(); if (p == -1) { perror("fork"); return 0; } else if (p == 0) { if (cpu_pids != NULL) { free(cpu_pids); cpu_pids = NULL; } if (disk_pids != NULL) { free(disk_pids); disk_pids = NULL; } mem_pid = 0; signal(SIGINT, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGCHLD, SIG_DFL); (*fn)(arg1, arg2, arg3, argP, argP2); exit(0); } else { say(1, "lookbusy (%d): %s started, PID %d\n", getpid(), desc, p); return p; } } static pid_t *start_cpu_spinners(int *ncpus, int util_l, int util_h) { pid_t *pids; if (*ncpus <= 0) *ncpus = get_cpu_count(); if ((pids = (pid_t *)malloc(sizeof(*pids) * *ncpus)) == NULL) { perror("malloc"); return NULL; } int i; say(1, "cpu_spin (%d): starting %d spinner(s) for %d%%-%d%% usage\n", getpid(), *ncpus, util_l, util_h); for (i = 0; i < *ncpus; i++) { pids[i] = fork_and_call("CPU spinner", cpu_spin, *ncpus, util_l, util_h, NULL, NULL); } return pids; } static pid_t *start_disk_stirrer(off_t util, char **paths, size_t paths_n) { size_t i; pid_t *p = malloc(sizeof(*p) * paths_n); for (i = 0; i < paths_n; i++) { struct stat st; if (stat(paths[i], &st) == 0) { if (S_ISDIR(st.st_mode)) { if (access(paths[i], W_OK) != 0) { err("disk path %s is not writable\n", paths[i]); shutdown(); } char *tmpl = (char *)malloc(strlen(paths[i]) + 32); if (tmpl == NULL) { perror("malloc"); shutdown(); } snprintf(tmpl, strlen(paths[i]) + 31, "%s/lb.%d.XXXXXX", paths[i], getpid()); int fd = mkstemp(tmpl); if (fd == -1) { perror("mkstemp"); free(tmpl); shutdown(); } free(paths[i]); paths[i] = tmpl; } } char *desc = (char *)malloc(32 + strlen(paths[i])); if (desc == NULL) { perror("malloc"); shutdown(); } snprintf(desc, 31 + strlen(paths[i]), "disk churn: %s", paths[i]); p[i] = fork_and_call(desc, disk_churn, 0, 0, 0, paths[i], &util); } return p; } static pid_t start_mem_whisker(size_t sz) { return fork_and_call("mem stirrer", mem_stir, sz, 0, 0, NULL, NULL); } static void usage() { static const char *msg = "lookbusy [ -h ] [ options ]\n" "General options:\n" " -h, --help Commandline help (you're reading it)\n" " -v, --verbose Verbose output (may be repeated)\n" " -q, --quiet Be quiet, produce output on errors only\n" "CPU usage options:\n" " -c, --cpu-util=PCT, Desired utilization of each CPU, in percent (default\n" " --cpu-util=RANGE 50%). If 'curve' CPU usage mode is chosen, a range\n" " of the form MIN-MAX should be given.\n" " -n, --ncpus=NUM Number of CPUs to keep busy (default: autodetected)\n" " -r, --cpu-mode=MODE Utilization mode ('fixed' or 'curve', see lookbusy(1))\n" " -p, --cpu-curve-peak=TIME\n" " Offset of peak utilization within curve period, in\n" " seconds (append 'm', 'h', 'd' for other units)\n" " -P, --cpu-curve-period=TIME\n" " Duration of utilization curve period, in seconds (append\n" " 'm', 'h', 'd' for other units)\n" "Memory usage options:\n" " -m, --mem-util=SIZE Amount of memory to use (in bytes, followed by KB, MB,\n" " or GB for other units; see lookbusy(1))\n" " -M, --mem-sleep=TIME Time to sleep between iterations, in usec (default 1000)\n" "Disk usage options:\n" " -d, --disk-util=SIZE Size of files to use for disk churn (in bytes,\n" " followed by KB, MB, GB or TB for other units)\n" " -b, --disk-block-size=SIZE\n" " Size of blocks to use for I/O (in bytes, followed\n" " by KB, MB or GB)\n" " -D, --disk-sleep=TIME\n" " Time to sleep between iterations, in msec (default 100)\n" " -f, --disk-path=PATH Path to a file/directory to use as a buffer (default\n" " /tmp); specify multiple times for additional paths\n" ""; printf("usage: %s", msg); exit(0); } int main(int argc, char **argv) { int c; size_t disk_paths_cap = 0; static const struct option long_options[] = { { "help", 0, NULL, 'h' }, { "verbose", 0, NULL, 'v' }, { "quiet", 0, NULL, 'q' }, { "version", 0, NULL, 'V' }, { "cpu-util", 1, NULL, 'c' }, { "cpu-mode", 1, NULL, 'r' }, { "ncpus", 1, NULL, 'n' }, { "cpu-curve-peak", 1, NULL, 'p' }, { "cpu-curve-period", 1, NULL, 'P' }, { "utc", 0, NULL, 'u' }, { "disk-util", 1, NULL, 'd' }, { "disk-sleep", 1, NULL, 'D' }, { "disk-block-size", 1, NULL, 'b' }, { "disk-path", 1, NULL, 'f' }, { "mem-util", 1, NULL, 'm' }, { "mem-sleep", 1, NULL, 'M' }, { 0, 0, 0, 0 } }; while ((c = getopt_long(argc, argv, "n:b:c:d:D:f:m:M:p:P:r:qvVhu", long_options, NULL)) != -1) { switch (c) { default: case 'h': usage(); break; case 'b': if (parse_size(optarg, &c_disk_churn_block_size) < 0) { err("Couldn't parse disk block size '%s'\n", optarg); return 1; } break; case 'c': if (parse_int_range(optarg, &c_cpu_util_l, &c_cpu_util_h) < 0) { err("Couldn't parse CPU utilization setting '%s'\n", optarg); return 1; } break; case 'd': if (parse_large_size(optarg, &c_disk_util) < 0) { err("Couldn't parse disk utilization filesize '%s'\n", optarg); return 1; } break; case 'D': c_disk_churn_sleep = atol(optarg); break; case 'f': if (!optarg || !*optarg) break; if (c_disk_churn_paths_n + 1 > disk_paths_cap) { disk_paths_cap = (c_disk_churn_paths_n + 1) * 2; char **tmp; tmp = (char **)realloc(c_disk_churn_paths, disk_paths_cap * sizeof(*tmp)); if (tmp == NULL) { perror("realloc"); _exit(1); } c_disk_churn_paths = tmp; } c_disk_churn_paths[c_disk_churn_paths_n++] = strdup(optarg); break; case 'm': if (parse_size(optarg, &c_mem_util) < 0) { err("Couldn't parse memory utilization size '%s'\n", optarg); return 1; } break; case 'M': c_mem_stir_sleep = atol(optarg); break; case 'n': ncpus = atoi(optarg); break; case 'p': if (parse_timespan(optarg, &c_cpu_curve_peak) < 0) { err("Couldn't parse CPU curve peak '%s'; format is" " INTEGER[SUFFIX], where SUFFIX\n" "is one of 's' (seconds), 'm' (minutes), 'h' (hours)" ", or 'd' (days); e.g. \"2h\"\n", optarg); return 1; } break; case 'P': if (parse_timespan(optarg, &c_cpu_curve_period) < 0) { err("Couldn't parse CPU curve period '%s'; format is" " INTEGER[SUFFIX], where SUFFIX\n" "is one of 's' (seconds), 'm' (minutes), 'h' (hours)" ", or 'd' (days); e.g. \"2h\"\n", optarg); return 1; } break; case 'q': verbosity = 0; break; case 'r': #ifdef HAVE_STRCASECMP if (strcasecmp(optarg, "fixed") == 0) c_cpu_util_mode = UTIL_MODE_FIXED; else if (strcasecmp(optarg, "curve") == 0) c_cpu_util_mode = UTIL_MODE_CURVE; #else if (strcmp(optarg, "fixed") == 0) c_cpu_util_mode = UTIL_MODE_FIXED; else if (strcmp(optarg, "curve") == 0) c_cpu_util_mode = UTIL_MODE_CURVE; #endif else { err("Unrecognized CPU utilization mode '%s'; choose one" " of 'fixed' or 'curve'\n", optarg); return 1; } break; case 'u': utc = ! utc; break; case 'v': verbosity++; break; case 'V': printf("%s %s -- %s\n", PACKAGE, VERSION, copyright); return 0; } } if (c_cpu_util_l != c_cpu_util_h && c_cpu_util_mode == UTIL_MODE_FIXED) { err("Fixed CPU usage mode selected, but CPU usage range %d-%d%%" " given; %d%% will\nbe used.\n", c_cpu_util_l, c_cpu_util_h, c_cpu_util_l); c_cpu_util_h = c_cpu_util_l; } if (c_cpu_util_mode == UTIL_MODE_CURVE && c_cpu_curve_peak > c_cpu_curve_period) { err("Peak of CPU usage curve is outside curve frequency" "(%ds > %ds)\n", c_cpu_curve_peak, c_cpu_curve_period); return 1; } if (c_disk_churn_paths == NULL && c_disk_util != 0) { c_disk_churn_paths = (char **)malloc(sizeof(*c_disk_churn_paths) * 1); *c_disk_churn_paths = strdup("/tmp"); c_disk_churn_paths_n = 1; } if (c_disk_util != 0 && c_disk_churn_block_size < 4) { err("Disk utilization block size must be at least 4 bytes\n"); return 1; } if (c_disk_util != 0 && c_disk_util < c_disk_churn_block_size) { err("Disk utilization size must be at least equal to the block size" " (%d < %d)\n" "You can adjust the block size with (-b or --disk-block-size)\n", c_disk_util, c_disk_churn_block_size); return 1; } signal(SIGCHLD, sigchld_handler); signal(SIGTERM, sigterm_handler); signal(SIGINT, sigterm_handler); if (ncpus != 0 && c_cpu_util_h != 0) { cpu_pids = start_cpu_spinners(&ncpus, c_cpu_util_l, c_cpu_util_h); // forks n_cpu_pids = ncpus; } if (c_disk_util != 0) { disk_pids = start_disk_stirrer(c_disk_util, c_disk_churn_paths, c_disk_churn_paths_n); // forks n_disk_pids = c_disk_churn_paths_n; } if (c_mem_util != 0) { mem_pid = start_mem_whisker(c_mem_util); // forks } while (sleep(1) == 0) say(2, "lookbusy (%d): waiting for spinners...\n", getpid()); shutdown(); return 0; } /* vi: set ts=4 sw=4 et: */