mirror of
https://github.com/10h30/lookbusy.git
synced 2026-05-12 15:21:20 +09:00
1096 lines
34 KiB
C
1096 lines
34 KiB
C
/* $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 <lookbusy@devin.com>.
|
|
* 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 <lookbusy@devin.com>\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 <stdio.h>
|
|
#ifdef HAVE_FCNTL_H
|
|
#include <fcntl.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#define _GNU_SOURCE
|
|
#include <getopt.h>
|
|
#ifdef HAVE_STRING_H
|
|
#include <string.h>
|
|
#endif
|
|
#include <sys/types.h>
|
|
#ifdef HAVE_SYS_WAIT_H
|
|
#include <sys/wait.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_TIME_H
|
|
#include <sys/time.h>
|
|
#endif
|
|
#ifdef HAVE_SYS_STAT_H
|
|
#include <sys/stat.h>
|
|
#endif
|
|
#include <time.h>
|
|
#include <signal.h>
|
|
#include <errno.h>
|
|
#include <inttypes.h>
|
|
#include <ctype.h>
|
|
#include <regex.h>
|
|
#include <stdarg.h>
|
|
#include <math.h>
|
|
|
|
#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: */
|