[Concept,13/20] buildman: Add dynamic job-count setting

Message ID 20260316154733.1587261-14-sjg@u-boot.org
State New
Headers
Series buildman: Add distributed builds |

Commit Message

Simon Glass March 16, 2026, 3:47 p.m. UTC
  From: Simon Glass <sjg@chromium.org>

When many boards are in flight on a high-thread machine, each make gets
only -j1 (nthreads / active_boards = 256/256), causing poor efficiency
when the machine is mostly idle.

Track the number of in-flight boards with a locked counter in
BuilderThread.run(). When Builder.num_jobs is None (meaning dynamic
mode), calculate -j as nthreads / active_boards. When the in-flight
count drops below the baseline (max_boards or nthreads), ramp it up by a
factor of two to use more of the available CPU power.

Rename _num_threads and _active_lock to public since BuilderThread
accesses them from a separate module.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

 tools/buildman/builder.py       | 16 ++++++++--------
 tools/buildman/builderthread.py | 19 +++++++++++++++++--
 2 files changed, 25 insertions(+), 10 deletions(-)
  

Patch

diff --git a/tools/buildman/builder.py b/tools/buildman/builder.py
index de895d43cfe..c4ec22dbc77 100644
--- a/tools/buildman/builder.py
+++ b/tools/buildman/builder.py
@@ -199,7 +199,7 @@  class Builder:
         _complete_delay: Expected delay until completion (timedelta)
         _next_delay_update: Next time we plan to display a progress update
                 (datatime)
-        _num_threads: Number of builder threads to run
+        num_threads: Number of builder threads to run
         _opts: DisplayOptions for result output
         _re_make_err: Compiled regex for make error detection
         _restarting_config: True if 'Restart config' is detected in output
@@ -304,11 +304,11 @@  class Builder:
         self.do_make = make_func or self.make
         self.gnu_make = gnu_make
         self.checkout = checkout
-        self._num_threads = num_threads
+        self.num_threads = num_threads
         self.num_jobs = num_jobs
         self.active_boards = 0
         self.max_boards = 0
-        self._active_lock = threading.Lock()
+        self.active_lock = threading.Lock()
         self._already_done = 0
         self.kconfig_reconfig = 0
         self.force_build = False
@@ -403,11 +403,11 @@  class Builder:
             test_thread_exceptions (bool): True to make threads raise an
                 exception instead of reporting their result (for tests)
         """
-        if self._num_threads:
+        if self.num_threads:
             self._single_builder = None
             self.queue = queue.Queue()
             self.out_queue = queue.Queue()
-            for i in range(self._num_threads):
+            for i in range(self.num_threads):
                 t = self._thread_class(
                         self, i, mrproper, per_board_out_dir,
                         test_exception=test_thread_exceptions)
@@ -1227,7 +1227,7 @@  class Builder:
 
         self._result_handler.reset_result_summary(board_selected)
         builderthread.mkdir(self.base_dir, parents = True)
-        self._prepare_working_space(min(self._num_threads, len(board_selected)),
+        self._prepare_working_space(min(self.num_threads, len(board_selected)),
                 board_selected and commits is not None)
         self._prepare_output_space()
         if not self._opts.ide:
@@ -1247,7 +1247,7 @@  class Builder:
             job.adjust_cfg = self.adjust_cfg
             job.fragments = fragments
             job.step = self._step
-            if self._num_threads:
+            if self.num_threads:
                 self.queue.put(job)
             else:
                 self._single_builder.run_job(job)
@@ -1268,7 +1268,7 @@  class Builder:
                 - number of boards that issued warnings
                 - list of thread exceptions raised
         """
-        if self._num_threads:
+        if self.num_threads:
             term = threading.Thread(target=self.queue.join)
             term.daemon = True
             term.start()
diff --git a/tools/buildman/builderthread.py b/tools/buildman/builderthread.py
index 13c98612c81..16534196d4d 100644
--- a/tools/buildman/builderthread.py
+++ b/tools/buildman/builderthread.py
@@ -332,8 +332,18 @@  class BuilderThread(threading.Thread):
             args.append('V=1')
         else:
             args.append('-s')
-        if self.builder.num_jobs is not None:
-            args.extend(['-j', str(self.builder.num_jobs)])
+        num_jobs = self.builder.num_jobs
+        if num_jobs is None and self.builder.active_boards:
+            active = self.builder.active_boards
+            nthreads = self.builder.num_threads
+            baseline = self.builder.max_boards or nthreads
+            if active < baseline:
+                # Tail: ramp up 2x to compensate for make overhead
+                num_jobs = max(1, nthreads * 2 // active)
+            else:
+                num_jobs = max(1, nthreads // active)
+        if num_jobs is not None:
+            args.extend(['-j', str(num_jobs)])
         if self.builder.warnings_as_errors:
             args.append('KCFLAGS=-Werror')
             args.append('HOSTCFLAGS=-Werror')
@@ -1022,10 +1032,15 @@  class BuilderThread(threading.Thread):
         """
         while True:
             job = self.builder.queue.get()
+            with self.builder.active_lock:
+                self.builder.active_boards += 1
             try:
                 self.run_job(job)
             except Exception as exc:  # pylint: disable=W0718
                 print('Thread exception (use -T0 to run without threads):',
                       exc)
                 self.builder.thread_exceptions.append(exc)
+            finally:
+                with self.builder.active_lock:
+                    self.builder.active_boards -= 1
             self.builder.queue.task_done()