diff -r -u make-4.3/src/job.c make-4.3.1/src/job.c --- make-4.3/src/job.c 2020-01-19 20:32:59.000000000 +0000 +++ make-4.3.1/src/job.c 2022-02-11 14:09:08.074210132 +0000 @@ -221,6 +221,7 @@ static void free_child (struct child *); static void start_job_command (struct child *child); static int load_too_high (void); +static int memory_too_high (void); static int job_next_command (struct child *); static int start_waiting_job (struct child *); @@ -1616,7 +1617,7 @@ /* If we are running at least one job already and the load average is too high, make this one wait. */ if (!c->remote - && ((job_slots_used > 0 && load_too_high ()) + && ((job_slots_used > 0 && (load_too_high () || memory_too_high ())) #ifdef WINDOWS32 || process_table_full () #endif @@ -2017,8 +2018,9 @@ OK, I'm not sure exactly how to handle that, but for sure we need to clamp this value at the number of cores before this can be enabled. */ -#define PROC_FD_INIT -1 - static int proc_fd = PROC_FD_INIT; +#ifdef linux + static int proc_fd = -2; +#endif double load, guess; time_t now; @@ -2032,6 +2034,7 @@ if (max_load_average < 0) return 0; +#ifdef linux /* If we haven't tried to open /proc/loadavg, try now. */ #define LOADAVG "/proc/loadavg" if (proc_fd == -2) @@ -2093,6 +2096,7 @@ close (proc_fd); proc_fd = -1; } +#endif /* linux */ /* Find the real system load average. */ make_access (); @@ -2138,6 +2142,111 @@ #endif } +static int +memory_too_high (void) +{ +#ifdef linux + static int proc_fd = -2; + + if (max_memory_used == 0.0) + return 0; + +#define MEMINFO "/proc/meminfo" + if (proc_fd == -2) + { + EINTRLOOP (proc_fd, open (MEMINFO, O_RDONLY)); + if (proc_fd < 0) + DB (DB_JOBS, ("Cannot use " MEMINFO " as memory use detection method.\n")); + else + { + DB (DB_JOBS, ("Using " MEMINFO " memory use detection method.\n")); + fd_noinherit (proc_fd); + } + } + + if (proc_fd >= 0) + { + int r; + + EINTRLOOP (r, lseek (proc_fd, 0, SEEK_SET)); + if (r >= 0) + { +#define PROC_MEMINFO_SIZE 256 + char mem[PROC_MEMINFO_SIZE+1]; + + EINTRLOOP (r, read (proc_fd, mem, PROC_MEMINFO_SIZE)); + if (r >= 0) + { + const char *p; + char *end; + double total = -1.0, avail = -1.0, used; + + /* The structure of /proc/meminfo is: + MemTotal: ... kB\n + MemFree: ... kB\n + MemAvailable: ... kB\n + ... + The difference between MemTotal and MemAvailable is + the amount of memory currently used. */ + mem[r] = '\0'; + p = strchr (mem, ':'); + if (p) + total = strtod (++p, &end); + if (end > p) + { + p = strchr (end, ':'); + if (p) + p = strchr (p+1, ':'); + if (p) + avail = strtod (p+1, &end); + } + used = total - avail; + if (total <= 0.0 || avail < 0.0 || used < 0.0) + { + /* The reported values in /proc/meminfo are invalid. */ + DB (DB_JOBS, ("Mem = %.3f kB, Avail = %.3f kB, Used = %.3f kB\n", + total, avail, used)); + return 1; + } + if (max_memory_used <= 1.0) + /* We are using percentages, so scale down. */ + used /= total; + if (used >= max_memory_used) + /* The limit has been reached, so no new jobs for now. */ + return 1; + if (max_load_average >= 1.0) + { + /* When a load average is specified together with a + memory limit do we scale the number of jobs with + the amount of memory used in order to stabilize + the memory use and to reduce load and memory + spikes. + + The function here creates a balance between speed + at a low memory use (creating slightly more jobs) + and linear scalability at a high memory use. + */ + double rt = used / max_memory_used; + + return job_slots_used + 1 >= + max_load_average * (1.0 - (2.0 - rt) * rt * rt); + } + return 0; + } + } + + /* If we got here, something went wrong. Give up on this method. */ + if (r < 0) + DB (DB_JOBS, ("Failed to read " MEMINFO ": %s\n", strerror (errno))); + + close (proc_fd); + proc_fd = -1; + } +#endif /* linux */ + + return 0; +} + /* Start jobs that are waiting for the load to be lower. */ void diff -r -u make-4.3/src/main.c make-4.3.1/src/main.c --- make-4.3/src/main.c 2020-01-19 20:32:59.000000000 +0000 +++ make-4.3.1/src/main.c 2022-02-11 14:20:07.372986002 +0000 @@ -36,6 +36,9 @@ #ifdef HAVE_STRINGS_H # include /* for strcasecmp */ #endif +#ifdef HAVE_STDLIB_H +# include /* for strtod */ +#endif # include "pathstuff.h" # include "sub_proc.h" # include "w32err.h" @@ -279,6 +282,13 @@ double max_load_average = -1.0; double default_load_average = -1.0; +/* Maximum memory usage upto which multiple jobs will be run. Zero + means unlimited, values between 0.0 and 1.0 are treated as a + percentage of total memory, greater values are treated as + absolute values in kilobytes. */ +const char *memory_used_arg = NULL; +double max_memory_used = 0.0; + /* List of directories given with -C switches. */ static struct stringlist *directories = 0; @@ -397,6 +407,9 @@ N_("\ --trace Print tracing information.\n"), N_("\ + -u [N], --memory-used=[N] Don't start multiple jobs when system memory use\n\ + is above N.\n"), + N_("\ -v, --version Print the version number of make and exit.\n"), N_("\ -w, --print-directory Print the current directory.\n"), @@ -451,6 +464,7 @@ &default_load_average, "load-average" }, { 'o', filename, &old_files, 0, 0, 0, 0, 0, "old-file" }, { 'O', string, &output_sync_option, 1, 1, 0, "target", 0, "output-sync" }, + { 'u', string, &memory_used_arg, 1, 1, 0, "0", "0", "memory-used" }, { 'W', filename, &new_files, 0, 0, 0, 0, 0, "what-if" }, /* These are long-style options. */ @@ -799,6 +813,58 @@ #endif } +/* Decode the -u, --memory-used argument. A number between 0.0 and 1.0 + is take as a percentage of the reported total memory. Numbers + greater 1.0 can be followed by a unit such as k, m, or g, or the + percent sign. Absolute values greater than 1.0 are represented in + kilobytes internally. */ + +static void +decode_memory_used_arg (void) +{ + if (memory_used_arg != NULL) + { + double value; + char *end; + + value = strtod (memory_used_arg, &end); + if (value > 0.0 && end != NULL && *end) + { + switch (tolower (*end)) + { + case 'k': /* kilo */ + break; + case 'm': /* mega */ + value *= (double) (1UL<<10); + break; + case 'g': /* giga */ + value *= (double) (1UL<<20); + break; + case 't': /* tera */ + value *= (double) (1UL<<30); + break; + case 'p': /* peta */ + value *= (double) (1UL<<40); + break; + case 'e': /* exa */ + value *= (double) (1UL<<50); + break; + case '%': + /* Treat percentage values greater 100% as invalid. */ + value *= (value <= 100.0) ? 0.01 : -1.0; + break; + default: + value /= 1024.0; + break; + } + } + if (value < 0.0) + OS (fatal, NILF, + _("Invalid system memory limit '%s'"), memory_used_arg); + max_memory_used = value; + } +} + #ifdef WINDOWS32 #ifndef NO_OUTPUT_SYNC @@ -3008,6 +3074,7 @@ /* If there are any options that need to be decoded do it now. */ decode_debug_flags (); decode_output_sync_flags (); + decode_memory_used_arg (); /* Perform any special switch handling. */ run_silent = silent_flag; diff -r -u make-4.3/src/makeint.h make-4.3.1/src/makeint.h --- make-4.3/src/makeint.h 2020-01-19 20:32:59.000000000 +0000 +++ make-4.3.1/src/makeint.h 2022-01-20 20:09:23.412527682 +0000 @@ -687,6 +687,7 @@ extern unsigned int job_slots; extern double max_load_average; +extern double max_memory_used; extern const char *program;