| /* A program to put stress on a POSIX system (stress). |
| * |
| * Copyright (C) 2001,2002,2003,2004,2005,2006,2007,2008,2009,2010 |
| * Amos Waterland <apw@rossby.metr.ou.edu> |
| * |
| * 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 <ctype.h> |
| #include <errno.h> |
| #include <libgen.h> |
| #include <math.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <sys/wait.h> |
| |
| /* By default, print all messages of severity info and above. */ |
| static int global_debug = 2; |
| |
| /* Name of this program */ |
| static char *global_progname = PACKAGE; |
| |
| /* Implemention of runtime-selectable severity message printing. */ |
| #define dbg(OUT, STR, ARGS...) if (global_debug >= 3) \ |
| fprintf (stdout, "%s: dbug: [%lli] ", \ |
| global_progname, (long long)getpid()), \ |
| fprintf(OUT, STR, ##ARGS), fflush(OUT) |
| #define out(OUT, STR, ARGS...) if (global_debug >= 2) \ |
| fprintf (stdout, "%s: info: [%lli] ", \ |
| global_progname, (long long)getpid()), \ |
| fprintf(OUT, STR, ##ARGS), fflush(OUT) |
| #define wrn(OUT, STR, ARGS...) if (global_debug >= 1) \ |
| fprintf (stderr, "%s: WARN: [%lli] (%d) ", \ |
| global_progname, (long long)getpid(), __LINE__), \ |
| fprintf(OUT, STR, ##ARGS), fflush(OUT) |
| #define err(OUT, STR, ARGS...) if (global_debug >= 0) \ |
| fprintf (stderr, "%s: FAIL: [%lli] (%d) ", \ |
| global_progname, (long long)getpid(), __LINE__), \ |
| fprintf(OUT, STR, ##ARGS), fflush(OUT) |
| |
| /* Implementation of check for option argument correctness. */ |
| #define assert_arg(A) \ |
| if (++i == argc || ((arg = argv[i])[0] == '-' && \ |
| !isdigit ((int)arg[1]) )) \ |
| { \ |
| err (stderr, "missing argument to option '%s'\n", A); \ |
| exit (1); \ |
| } |
| |
| /* Prototypes for utility functions. */ |
| int usage (int status); |
| int version (int status); |
| long long atoll_s (const char *nptr); |
| long long atoll_b (const char *nptr); |
| |
| /* Prototypes for worker functions. */ |
| int hogcpu (void); |
| int hogio (void); |
| int hogvm (long long bytes, long long stride, long long hang, int keep); |
| int hoghdd (long long bytes); |
| |
| int |
| main (int argc, char **argv) |
| { |
| int i, pid, children = 0, retval = 0; |
| long starttime, stoptime, runtime, forks; |
| |
| /* Variables that indicate which options have been selected. */ |
| int do_dryrun = 0; |
| long long do_backoff = 3000; |
| long long do_timeout = 0; |
| long long do_cpu = 0; |
| long long do_io = 0; |
| long long do_vm = 0; |
| long long do_vm_bytes = 256 * 1024 * 1024; |
| long long do_vm_stride = 4096; |
| long long do_vm_hang = -1; |
| int do_vm_keep = 0; |
| long long do_hdd = 0; |
| long long do_hdd_bytes = 1024 * 1024 * 1024; |
| |
| /* Record our start time. */ |
| if ((starttime = time (NULL)) == -1) |
| { |
| err (stderr, "failed to acquire current time: %s\n", strerror (errno)); |
| exit (1); |
| } |
| |
| /* SuSv3 does not define any error conditions for this function. */ |
| global_progname = basename (argv[0]); |
| |
| /* For portability, parse command line options without getopt_long. */ |
| for (i = 1; i < argc; i++) |
| { |
| char *arg = argv[i]; |
| |
| if (strcmp (arg, "--help") == 0 || strcmp (arg, "-?") == 0) |
| { |
| usage (0); |
| } |
| else if (strcmp (arg, "--version") == 0) |
| { |
| version (0); |
| } |
| else if (strcmp (arg, "--verbose") == 0 || strcmp (arg, "-v") == 0) |
| { |
| global_debug = 3; |
| } |
| else if (strcmp (arg, "--quiet") == 0 || strcmp (arg, "-q") == 0) |
| { |
| global_debug = 0; |
| } |
| else if (strcmp (arg, "--dry-run") == 0 || strcmp (arg, "-n") == 0) |
| { |
| do_dryrun = 1; |
| } |
| else if (strcmp (arg, "--backoff") == 0) |
| { |
| assert_arg ("--backoff"); |
| if (sscanf (arg, "%lli", &do_backoff) != 1) |
| { |
| err (stderr, "invalid number: %s\n", arg); |
| exit (1); |
| } |
| if (do_backoff < 0) |
| { |
| err (stderr, "invalid backoff factor: %lli\n", do_backoff); |
| exit (1); |
| } |
| dbg (stdout, "setting backoff coeffient to %llius\n", do_backoff); |
| } |
| else if (strcmp (arg, "--timeout") == 0 || strcmp (arg, "-t") == 0) |
| { |
| assert_arg ("--timeout"); |
| do_timeout = atoll_s (arg); |
| if (do_timeout <= 0) |
| { |
| err (stderr, "invalid timeout value: %llis\n", do_timeout); |
| exit (1); |
| } |
| } |
| else if (strcmp (arg, "--cpu") == 0 || strcmp (arg, "-c") == 0) |
| { |
| assert_arg ("--cpu"); |
| do_cpu = atoll_b (arg); |
| if (do_cpu <= 0) |
| { |
| err (stderr, "invalid number of cpu hogs: %lli\n", do_cpu); |
| exit (1); |
| } |
| } |
| else if (strcmp (arg, "--io") == 0 || strcmp (arg, "-i") == 0) |
| { |
| assert_arg ("--io"); |
| do_io = atoll_b (arg); |
| if (do_io <= 0) |
| { |
| err (stderr, "invalid number of io hogs: %lli\n", do_io); |
| exit (1); |
| } |
| } |
| else if (strcmp (arg, "--vm") == 0 || strcmp (arg, "-m") == 0) |
| { |
| assert_arg ("--vm"); |
| do_vm = atoll_b (arg); |
| if (do_vm <= 0) |
| { |
| err (stderr, "invalid number of vm hogs: %lli\n", do_vm); |
| exit (1); |
| } |
| } |
| else if (strcmp (arg, "--vm-bytes") == 0) |
| { |
| assert_arg ("--vm-bytes"); |
| do_vm_bytes = atoll_b (arg); |
| if (do_vm_bytes <= 0) |
| { |
| err (stderr, "invalid vm byte value: %lli\n", do_vm_bytes); |
| exit (1); |
| } |
| } |
| else if (strcmp (arg, "--vm-stride") == 0) |
| { |
| assert_arg ("--vm-stride"); |
| do_vm_stride = atoll_b (arg); |
| if (do_vm_stride <= 0) |
| { |
| err (stderr, "invalid stride value: %lli\n", do_vm_stride); |
| exit (1); |
| } |
| } |
| else if (strcmp (arg, "--vm-hang") == 0) |
| { |
| assert_arg ("--vm-hang"); |
| do_vm_hang = atoll_b (arg); |
| if (do_vm_hang < 0) |
| { |
| err (stderr, "invalid value: %lli\n", do_vm_hang); |
| exit (1); |
| } |
| } |
| else if (strcmp (arg, "--vm-keep") == 0) |
| { |
| do_vm_keep = 1; |
| } |
| else if (strcmp (arg, "--hdd") == 0 || strcmp (arg, "-d") == 0) |
| { |
| assert_arg ("--hdd"); |
| do_hdd = atoll_b (arg); |
| if (do_hdd <= 0) |
| { |
| err (stderr, "invalid number of hdd hogs: %lli\n", do_hdd); |
| exit (1); |
| } |
| } |
| else if (strcmp (arg, "--hdd-bytes") == 0) |
| { |
| assert_arg ("--hdd-bytes"); |
| do_hdd_bytes = atoll_b (arg); |
| if (do_hdd_bytes <= 0) |
| { |
| err (stderr, "invalid hdd byte value: %lli\n", do_hdd_bytes); |
| exit (1); |
| } |
| } |
| else |
| { |
| err (stderr, "unrecognized option: %s\n", arg); |
| exit (1); |
| } |
| } |
| |
| /* Print startup message if we have work to do, bail otherwise. */ |
| if (do_cpu + do_io + do_vm + do_hdd) |
| { |
| out (stdout, "dispatching hogs: %lli cpu, %lli io, %lli vm, %lli hdd\n", |
| do_cpu, do_io, do_vm, do_hdd); |
| } |
| else |
| usage (0); |
| |
| /* Round robin dispatch our worker processes. */ |
| while ((forks = (do_cpu + do_io + do_vm + do_hdd))) |
| { |
| long long backoff, timeout = 0; |
| |
| /* Calculate the backoff value so we get good fork throughput. */ |
| backoff = do_backoff * forks; |
| dbg (stdout, "using backoff sleep of %llius\n", backoff); |
| |
| /* If we are supposed to respect a timeout, calculate it. */ |
| if (do_timeout) |
| { |
| long long currenttime; |
| |
| /* Acquire current time. */ |
| if ((currenttime = time (NULL)) == -1) |
| { |
| perror ("error acquiring current time"); |
| exit (1); |
| } |
| |
| /* Calculate timeout based on current time. */ |
| timeout = do_timeout - (currenttime - starttime); |
| |
| if (timeout > 0) |
| { |
| dbg (stdout, "setting timeout to %llis\n", timeout); |
| } |
| else |
| { |
| wrn (stderr, "used up time before all workers dispatched\n"); |
| break; |
| } |
| } |
| |
| if (do_cpu) |
| { |
| switch (pid = fork ()) |
| { |
| case 0: /* child */ |
| alarm (timeout); |
| usleep (backoff); |
| if (do_dryrun) |
| exit (0); |
| exit (hogcpu ()); |
| case -1: /* error */ |
| err (stderr, "fork failed: %s\n", strerror (errno)); |
| break; |
| default: /* parent */ |
| dbg (stdout, "--> hogcpu worker %lli [%i] forked\n", |
| do_cpu, pid); |
| ++children; |
| } |
| --do_cpu; |
| } |
| |
| if (do_io) |
| { |
| switch (pid = fork ()) |
| { |
| case 0: /* child */ |
| alarm (timeout); |
| usleep (backoff); |
| if (do_dryrun) |
| exit (0); |
| exit (hogio ()); |
| case -1: /* error */ |
| err (stderr, "fork failed: %s\n", strerror (errno)); |
| break; |
| default: /* parent */ |
| dbg (stdout, "--> hogio worker %lli [%i] forked\n", do_io, pid); |
| ++children; |
| } |
| --do_io; |
| } |
| |
| if (do_vm) |
| { |
| switch (pid = fork ()) |
| { |
| case 0: /* child */ |
| alarm (timeout); |
| usleep (backoff); |
| if (do_dryrun) |
| exit (0); |
| exit (hogvm |
| (do_vm_bytes, do_vm_stride, do_vm_hang, do_vm_keep)); |
| case -1: /* error */ |
| err (stderr, "fork failed: %s\n", strerror (errno)); |
| break; |
| default: /* parent */ |
| dbg (stdout, "--> hogvm worker %lli [%i] forked\n", do_vm, pid); |
| ++children; |
| } |
| --do_vm; |
| } |
| |
| if (do_hdd) |
| { |
| switch (pid = fork ()) |
| { |
| case 0: /* child */ |
| alarm (timeout); |
| usleep (backoff); |
| if (do_dryrun) |
| exit (0); |
| exit (hoghdd (do_hdd_bytes)); |
| case -1: /* error */ |
| err (stderr, "fork failed: %s\n", strerror (errno)); |
| break; |
| default: /* parent */ |
| dbg (stdout, "--> hoghdd worker %lli [%i] forked\n", |
| do_hdd, pid); |
| ++children; |
| } |
| --do_hdd; |
| } |
| } |
| |
| /* Wait for our children to exit. */ |
| while (children) |
| { |
| int status, ret; |
| |
| if ((pid = wait (&status)) > 0) |
| { |
| --children; |
| |
| if (WIFEXITED (status)) |
| { |
| if ((ret = WEXITSTATUS (status)) == 0) |
| { |
| dbg (stdout, "<-- worker %i returned normally\n", pid); |
| } |
| else |
| { |
| err (stderr, "<-- worker %i returned error %i\n", pid, ret); |
| ++retval; |
| wrn (stderr, "now reaping child worker processes\n"); |
| if (signal (SIGUSR1, SIG_IGN) == SIG_ERR) |
| err (stderr, "handler error: %s\n", strerror (errno)); |
| if (kill (-1 * getpid (), SIGUSR1) == -1) |
| err (stderr, "kill error: %s\n", strerror (errno)); |
| } |
| } |
| else if (WIFSIGNALED (status)) |
| { |
| if ((ret = WTERMSIG (status)) == SIGALRM) |
| { |
| dbg (stdout, "<-- worker %i signalled normally\n", pid); |
| } |
| else if ((ret = WTERMSIG (status)) == SIGUSR1) |
| { |
| dbg (stdout, "<-- worker %i reaped\n", pid); |
| } |
| else |
| { |
| err (stderr, "<-- worker %i got signal %i\n", pid, ret); |
| ++retval; |
| wrn (stderr, "now reaping child worker processes\n"); |
| if (signal (SIGUSR1, SIG_IGN) == SIG_ERR) |
| err (stderr, "handler error: %s\n", strerror (errno)); |
| if (kill (-1 * getpid (), SIGUSR1) == -1) |
| err (stderr, "kill error: %s\n", strerror (errno)); |
| } |
| } |
| else |
| { |
| err (stderr, "<-- worker %i exited abnormally\n", pid); |
| ++retval; |
| } |
| } |
| else |
| { |
| err (stderr, "error waiting for worker: %s\n", strerror (errno)); |
| ++retval; |
| break; |
| } |
| } |
| |
| /* Record our stop time. */ |
| if ((stoptime = time (NULL)) == -1) |
| { |
| err (stderr, "failed to acquire current time\n"); |
| exit (1); |
| } |
| |
| /* Calculate our runtime. */ |
| runtime = stoptime - starttime; |
| |
| /* Print final status message. */ |
| if (retval) |
| { |
| err (stderr, "failed run completed in %lis\n", runtime); |
| } |
| else |
| { |
| out (stdout, "successful run completed in %lis\n", runtime); |
| } |
| |
| exit (retval); |
| } |
| |
| int |
| hogcpu (void) |
| { |
| while (1) |
| sqrt (rand ()); |
| |
| return 0; |
| } |
| |
| int |
| hogio () |
| { |
| while (1) |
| sync (); |
| |
| return 0; |
| } |
| |
| int |
| hogvm (long long bytes, long long stride, long long hang, int keep) |
| { |
| long long i; |
| char *ptr = 0; |
| char c; |
| int do_malloc = 1; |
| |
| while (1) |
| { |
| if (do_malloc) |
| { |
| dbg (stdout, "allocating %lli bytes ...\n", bytes); |
| if (!(ptr = (char *) malloc (bytes * sizeof (char)))) |
| { |
| err (stderr, "hogvm malloc failed: %s\n", strerror (errno)); |
| return 1; |
| } |
| if (keep) |
| do_malloc = 0; |
| } |
| |
| dbg (stdout, "touching bytes in strides of %lli bytes ...\n", stride); |
| for (i = 0; i < bytes; i += stride) |
| ptr[i] = 'Z'; /* Ensure that COW happens. */ |
| |
| if (hang == 0) |
| { |
| dbg (stdout, "sleeping forever with allocated memory\n"); |
| while (1) |
| sleep (1024); |
| } |
| else if (hang > 0) |
| { |
| dbg (stdout, "sleeping for %llis with allocated memory\n", hang); |
| sleep (hang); |
| } |
| |
| for (i = 0; i < bytes; i += stride) |
| { |
| c = ptr[i]; |
| if (c != 'Z') |
| { |
| err (stderr, "memory corruption at: %p\n", ptr + i); |
| return 1; |
| } |
| } |
| |
| if (do_malloc) |
| { |
| free (ptr); |
| dbg (stdout, "freed %lli bytes\n", bytes); |
| } |
| } |
| |
| return 0; |
| } |
| |
| int |
| hoghdd (long long bytes) |
| { |
| long long i, j; |
| int fd; |
| int chunk = (1024 * 1024) - 1; /* Minimize slow writing. */ |
| char buff[chunk]; |
| |
| /* Initialize buffer with some random ASCII data. */ |
| dbg (stdout, "seeding %d byte buffer with random data\n", chunk); |
| for (i = 0; i < chunk - 1; i++) |
| { |
| j = rand (); |
| j = (j < 0) ? -j : j; |
| j %= 95; |
| j += 32; |
| buff[i] = j; |
| } |
| buff[i] = '\n'; |
| |
| while (1) |
| { |
| char name[] = "./stress.XXXXXX"; |
| |
| if ((fd = mkstemp (name)) == -1) |
| { |
| err (stderr, "mkstemp failed: %s\n", strerror (errno)); |
| return 1; |
| } |
| |
| dbg (stdout, "opened %s for writing %lli bytes\n", name, bytes); |
| |
| dbg (stdout, "unlinking %s\n", name); |
| if (unlink (name) == -1) |
| { |
| err (stderr, "unlink of %s failed: %s\n", name, strerror (errno)); |
| return 1; |
| } |
| |
| dbg (stdout, "fast writing to %s\n", name); |
| for (j = 0; bytes == 0 || j + chunk < bytes; j += chunk) |
| { |
| if (write (fd, buff, chunk) == -1) |
| { |
| err (stderr, "write failed: %s\n", strerror (errno)); |
| return 1; |
| } |
| } |
| |
| dbg (stdout, "slow writing to %s\n", name); |
| for (; bytes == 0 || j < bytes - 1; j++) |
| { |
| if (write (fd, &buff[j % chunk], 1) == -1) |
| { |
| err (stderr, "write failed: %s\n", strerror (errno)); |
| return 1; |
| } |
| } |
| if (write (fd, "\n", 1) == -1) |
| { |
| err (stderr, "write failed: %s\n", strerror (errno)); |
| return 1; |
| } |
| ++j; |
| |
| dbg (stdout, "closing %s after %lli bytes\n", name, j); |
| close (fd); |
| } |
| |
| return 0; |
| } |
| |
| /* Convert a string representation of a number with an optional size suffix |
| * to a long long. |
| */ |
| long long |
| atoll_b (const char *nptr) |
| { |
| int pos; |
| char suffix; |
| long long factor = 0; |
| long long value; |
| |
| if ((pos = strlen (nptr) - 1) < 0) |
| { |
| err (stderr, "invalid string\n"); |
| exit (1); |
| } |
| |
| switch (suffix = nptr[pos]) |
| { |
| case 'b': |
| case 'B': |
| factor = 0; |
| break; |
| case 'k': |
| case 'K': |
| factor = 10; |
| break; |
| case 'm': |
| case 'M': |
| factor = 20; |
| break; |
| case 'g': |
| case 'G': |
| factor = 30; |
| break; |
| default: |
| if (suffix < '0' || suffix > '9') |
| { |
| err (stderr, "unrecognized suffix: %c\n", suffix); |
| exit (1); |
| } |
| } |
| |
| if (sscanf (nptr, "%lli", &value) != 1) |
| { |
| err (stderr, "invalid number: %s\n", nptr); |
| exit (1); |
| } |
| |
| value = value << factor; |
| |
| return value; |
| } |
| |
| /* Convert a string representation of a number with an optional time suffix |
| * to a long long. |
| */ |
| long long |
| atoll_s (const char *nptr) |
| { |
| int pos; |
| char suffix; |
| long long factor = 1; |
| long long value; |
| |
| if ((pos = strlen (nptr) - 1) < 0) |
| { |
| err (stderr, "invalid string\n"); |
| exit (1); |
| } |
| |
| switch (suffix = nptr[pos]) |
| { |
| case 's': |
| case 'S': |
| factor = 1; |
| break; |
| case 'm': |
| case 'M': |
| factor = 60; |
| break; |
| case 'h': |
| case 'H': |
| factor = 60 * 60; |
| break; |
| case 'd': |
| case 'D': |
| factor = 60 * 60 * 24; |
| break; |
| case 'y': |
| case 'Y': |
| factor = 60 * 60 * 24 * 365; |
| break; |
| default: |
| if (suffix < '0' || suffix > '9') |
| { |
| err (stderr, "unrecognized suffix: %c\n", suffix); |
| exit (1); |
| } |
| } |
| |
| if (sscanf (nptr, "%lli", &value) != 1) |
| { |
| err (stderr, "invalid number: %s\n", nptr); |
| exit (1); |
| } |
| |
| value = value * factor; |
| |
| return value; |
| } |
| |
| int |
| version (int status) |
| { |
| char *mesg = "%s %s\n"; |
| |
| fprintf (stdout, mesg, global_progname, VERSION); |
| |
| if (status <= 0) |
| exit (-1 * status); |
| |
| return 0; |
| } |
| |
| int |
| usage (int status) |
| { |
| char *mesg = |
| "`%s' imposes certain types of compute stress on your system\n\n" |
| "Usage: %s [OPTION [ARG]] ...\n" |
| " -?, --help show this help statement\n" |
| " --version show version statement\n" |
| " -v, --verbose be verbose\n" |
| " -q, --quiet be quiet\n" |
| " -n, --dry-run show what would have been done\n" |
| " -t, --timeout N timeout after N seconds\n" |
| " --backoff N wait factor of N microseconds before work starts\n" |
| " -c, --cpu N spawn N workers spinning on sqrt()\n" |
| " -i, --io N spawn N workers spinning on sync()\n" |
| " -m, --vm N spawn N workers spinning on malloc()/free()\n" |
| " --vm-bytes B malloc B bytes per vm worker (default is 256MB)\n" |
| " --vm-stride B touch a byte every B bytes (default is 4096)\n" |
| " --vm-hang N sleep N secs before free (default none, 0 is inf)\n" |
| " --vm-keep redirty memory instead of freeing and reallocating\n" |
| " -d, --hdd N spawn N workers spinning on write()/unlink()\n" |
| " --hdd-bytes B write B bytes per hdd worker (default is 1GB)\n\n" |
| "Example: %s --cpu 8 --io 4 --vm 2 --vm-bytes 128M --timeout 10s\n\n" |
| "Note: Numbers may be suffixed with s,m,h,d,y (time) or B,K,M,G (size).\n"; |
| |
| fprintf (stdout, mesg, global_progname, global_progname, global_progname); |
| |
| if (status <= 0) |
| exit (-1 * status); |
| |
| return 0; |
| } |