Hi tech@,

Inspired by the flurry of changes to cron(8) the last couple of days, I
finally went to fix an issue that has bitten me numerous times over the
years, since I'm a heavy user of batch(1) and at(1):

When running a batch/at job, cron looks at the system's current load
average to determine if it can run the job now or needs to wait until
the system is more lightly loaded.

The default load_avg is set to 1.5, which is often way too low in a
modern system with perhaps thousands of processes running or waiting to
run on dozens of cpu cores. I often forget to change the default on new
machines (hence the frequency of me messing this up).


This patch changes the default setting to 1.5 *
(number_of_cpus_in_system) instead, which I find better matches modern
behaviour.

I took inspiration from FreeBSD's atrun(8) that does the same. We
(thankfully) don't have a separate atrun, it is rather integrated in the
cron daemon.

I also added a -L switch that works like cron -l in that it adjusts the
load average used, but uses the same scaling as I do when calculating
the default, so that for example "cron -L 2.5" on a 12-core machine
gives a max load_avg of 30, while "cron -l 2.5" still gets the same 2.5
as it always has.


The easiest solution to this is perhaps to simply set the default to
0.0, disabling the resource check altogether. But I rather like that it
is there and works, as a safeguard - it just needs a default that is
better adapted to modern computing, and I feel that this approach would
add minimal complexity while still retaining a useful default in most
situations.


Thoughts?


Regards,

/Benny



Index: cron.8
===================================================================
RCS file: /cvs/src/usr.sbin/cron/cron.8,v
retrieving revision 1.32
diff -u -p -u -r1.32 cron.8
--- cron.8      23 Jan 2015 01:01:06 -0000      1.32
+++ cron.8      13 Nov 2015 22:47:27 -0000
@@ -29,6 +29,7 @@
 .Nm cron
 .Op Fl n
 .Op Fl l Ar load_avg
+.Op Fl L Ar scaled_load_avg
 .Sh DESCRIPTION
 The
 .Nm
@@ -116,10 +117,14 @@ If the current load average is greater t
 .Ar load_avg ,
 .Xr batch 1
 jobs will not be run.
-The default value is 1.5.
+The default value is 1.5 * number of cpus present in the system.
 To allow
 .Xr batch 1
 jobs to run regardless of the load, a value of 0.0 may be used.
+.It Fl L Ar scaled_load_avg
+Similar to -l, but
+.Ar scaled_load_avg
+is multiplied by the number of cpus before being set.
 .It Fl n
 By default,
 .Nm
Index: cron.c
===================================================================
RCS file: /cvs/src/usr.sbin/cron/cron.c,v
retrieving revision 1.66
diff -u -p -u -r1.66 cron.c
--- cron.c      9 Nov 2015 16:37:07 -0000       1.66
+++ cron.c      13 Nov 2015 22:47:27 -0000
@@ -17,9 +17,10 @@
  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */

-#include <sys/types.h>
+#include <sys/param.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
+#include <sys/sysctl.h>
 #include <sys/un.h>
 #include <sys/wait.h>

@@ -53,7 +54,8 @@ static        void    usage(void),
                sigchld_reaper(void),
                parse_args(int c, char *v[]);

-static int     open_socket(void);
+static int     open_socket(void),
+               getncpu(void);

 static volatile sig_atomic_t   got_sighup, got_sigchld;
 static time_t                  timeRunning, virtualTime, clockTime;
@@ -61,24 +63,50 @@ static      int                     cronSock;
 static long                    GMToff;
 static cron_db                 *database;
 static at_db                   *at_database;
-static double                  batch_maxload = BATCH_MAXLOAD;
+static double                  batch_maxload;
 static int                     NoFork;
 static time_t                  StartTime;
+static int                     ncpus;

 static void
 usage(void)
 {

-       fprintf(stderr, "usage: %s [-n] [-l load_avg]\n", __progname);
+       fprintf(stderr,
+               "usage: %s [-n] [-l load_avg] [-L scaled_load_avg]\n",
+               __progname);
        exit(EXIT_FAILURE);
 }

+
+static int
+getncpu(void)  /* From top(1), machine.c */
+{
+        int mib[] = { CTL_HW, HW_NCPU };
+        int ncpu;
+        size_t size = sizeof(ncpu);
+
+        if (sysctl(mib, sizeof(mib) / sizeof(mib[0]),
+            &ncpu, &size, NULL, 0) == -1)
+                return -1;
+
+        return ncpu;
+}
+
+
 int
 main(int argc, char *argv[])
 {
        struct sigaction sact;
        sigset_t blocked, omask;

+       ncpus = getncpu(); /* Get this early, before pledge() */
+       if (ncpus == -1)
+           ncpus = 1; /* Silently assume SP if sysctl() call fails */
+
+       /* Scale default value by number of cpus present */
+       batch_maxload = BATCH_MAXLOAD * ncpus;
+
        setlocale(LC_ALL, "");

        setvbuf(stdout, NULL, _IOLBF, 0);
@@ -509,6 +537,16 @@ parse_args(int argc, char *argv[])
                case 'l':
                        errno = 0;
                        batch_maxload = strtod(optarg, &ep);
+                       if (*ep != '\0' || ep == optarg || errno == ERANGE ||
+                           batch_maxload < 0) {
+                               fprintf(stderr, "Illegal load average: %s\n",
+                                   optarg);
+                               usage();
+                       }
+                       break;
+               case 'L': /* Same as -l but value is scaled by no of cpus */
+                       errno = 0;
+                       batch_maxload = strtod(optarg, &ep) * ncpus;
                        if (*ep != '\0' || ep == optarg || errno == ERANGE ||
                            batch_maxload < 0) {
                                fprintf(stderr, "Illegal load average: %s\n",

Reply via email to