27

I've been searching for an hour, and this information appears to be nowhere...

I'd like to be able to extract (and possibly use) the number of requested make "jobs," as passed via the -j option, or by Make itself in the case of sub-makes, in the Makefile.

The most promising thing I've seen so far is the $(MAKEFLAGS) variable, but on my system (if I do, say, make -j2) the contents of this variable are only "--jobserver-fds=3,4 -j". Is there any way to get the actual number of jobs passed with -j?

JW Peterson
  • 285
  • 3
  • 6
  • 1
    Why do you want to do that? This is so against the spirit of make, I'm pretty sure there is a better way to achieve what you want to do. – static_rtti Mar 14 '11 at 20:29
  • You could make the number of jobs a variable parameter in your top-level Makefile, which would internally invoke `make -j`. – aaz Mar 14 '11 at 20:54
  • @static_rtti, I was hoping to learn a bit more about the actual execution of make (in particular when it invokes itself with $(MAKE)). For example, do all sub-makes have a job_slots value of 1 or can sub makes be passed some subset of the N jobs available at top level? Reading the source as Raphael suggested is probably the way to determine this, but I thought experimenting with make itself might be simpler. – JW Peterson Mar 15 '11 at 18:58
  • 2
    The arguments to --jobserver-fds are the file descriptors used to pass the job tokens around. There's a good description of the whole scheme at http://mad-scientist.net/make/jobserver.html. – rakslice Sep 20 '12 at 01:24
  • Related: http://stackoverflow.com/q/5303553/946850 – krlmlr Nov 18 '13 at 15:03

3 Answers3

15

Actually there is a way to implement this completely inside your makefile on *nix.

MAKE_PID := $(shell echo $$PPID)
JOB_FLAG := $(filter -j%, $(subst -j ,-j,$(shell ps T | grep "^\s*$(MAKE_PID).*$(MAKE)")))
JOBS     := $(subst -j,,$(JOB_FLAG))

grep also needs to be installed, but it is almost a given. It can be further improved to handle --jobs too.

A version using regex and supporting --jobs, as requested in the comments:

MAKE_PID := $(shell echo $$PPID)
JOBS := $(shell ps T | sed -n 's%.*$(MAKE_PID).*$(MAKE).* \(-j\|--jobs=\) *\([0-9][0-9]*\).*%\2%p')
tripleee
  • 175,061
  • 34
  • 275
  • 318
yashma
  • 335
  • 2
  • 6
  • I just tried this, and it works great! I might make one small change to set `$JOB_FLAG` to 1 if the user did not pass any `-j` to make. Thanks! – JW Peterson Dec 16 '15 at 17:13
  • Instead of grepping the output of `ps` you can also read `/proc/$PPID/cmdline`. – Weijun Zhou Jan 05 '18 at 07:22
  • This doesn't handle a bare `-j` well. For example, `make -j help` will result in a `$(JOB_FLAG)` of `-jhelp`. – CivFan May 02 '18 at 01:32
  • If you're already using `ps` and `grep`, why not just do it all in a regex, rather than using the `make` functions? – CivFan May 07 '18 at 19:29
  • I don't think this will work for recursive `make` with a jobserver; one of the reason to read parallelism level is to know how many jobs a jobserver has assigned to current instance of `make`. – Victor Sergienko Feb 18 '19 at 22:21
14

I'm sorry but there is no way to identify the number of parallel jobs -- without writing an application or script that scan the process list to identify the calling parameters.

Check the source code at http://cvs.savannah.gnu.org/viewvc/make/main.c?revision=1.246&root=make&view=markup . Search for job_slots > 1.

Update: If you have control over the operating range you could wrap the make application with your own program/script, parse the parameters, set an dedicated environment variable and call the original make afterwards.

Mike Seplowitz
  • 9,785
  • 1
  • 24
  • 23
Raphael Bossek
  • 1,904
  • 14
  • 25
  • Hi Raphael, Thanks for your answer; it's what I was afraid of! For now I was simply able to modify my Makefile so that the actual value of -j was not needed... which I guess is more "in the spirit of make" ;-P – JW Peterson Mar 15 '11 at 18:55
2

Using the job-server FDs to deduce the actual available job count

I've come up with a different approach which I prefer over parsing the command line as it was suggested in the accepted answer. The main issue I have with the latter is that it doesn't work in a recursively called makefile, as the jobs parameter is passed via the env there, and then it's representing the jobserver which tells you nothing of how many job tokens it's going to provide

So I have decided to embrace the jobserver thing and make a small Python script that consumes all tokens from the jobserver, counts them, and then puts them back.

This was a neat idea that appeared to be surprisingly easy to implement, and it worked very well for me.

There are a few notes on how it should be used:

  1. the script must be called with a + prefix on the recipe because that enables the "submake" functionality which duplicates the jobserver descriptors for the child process to be able to "spawn workers"
  2. to give the correct job count the rule containing this script in the recipe should be made a common order-only phony prerequisite to all your targets so that it will be run by make prior to anything else and never in parallel to other things, cause then the count would be off
  3. The current script implementation will max out at 1024 tokens. Feel free to improve the code to avoid the limitation, can be done with a simple loop around the read and a little logic.

The Python script:


import argparse, os

def safe_int(s):
    try:
        return int(s)
    except:
        return -1

class JobserverArgs:
    known_names = ["jobserver-fds","jobserver-auth"]
    def __init__(self):
        self.fds = "-1,-1"

    @staticmethod
    def from_argv():
        ja = JobserverArgs()
        parser = argparse.ArgumentParser()
        for name in JobserverArgs.known_names:
            parser.add_argument('--'+name, dest="fds")
        parser.parse_known_args(namespace=ja)
        return ja

    def get_fds(self):
        return tuple([safe_int(fd) for fd in (self.fds+",").split(",")][:2])


fd_in, fd_out = JobserverArgs.from_argv().get_fds()

if fd_in == -1 or fd_out == -1:
    print(1)
else:
    os.set_blocking(fd_in, False)

    tokens = os.read(fd_in, 1024)
    os.write(fd_out, tokens)

    print(len(tokens)+1)

example makefile:

TARGETS := a b c d e

.PHONY: $(TARGETS)
$(TARGETS):
    @for i in 1 2; do echo "$@$$i"; sleep 1; done

.PHONY: all
all: $(TARGETS)
    @echo done all

$(TARGETS): | common

.PHONY: common
common:
    +$(eval JOBS:=$$(shell python jobs.py $(MAKEFLAGS)))
    @echo JOBS=$(JOBS)

running it:

projects/make-fun » make all -j100
JOBS=100
a1
b1
c1
d1
e1
a2
b2
c2
d2
e2
done all
Leonid Usov
  • 1,508
  • 12
  • 16